Kann der Parameter einer Java-Funktion optionale Schnittstellen erfordern?

8

Ich habe zwei Schnittstellen in Java (Version 8), die sehr ähnlich sind. Ich kann die Schnittstellen nicht ändern und kann die Klassen, die sie implementieren, nicht ändern.

public interface A {
    int get();
}

public interface B {
    int get();
    int somethingelse();
}

Jetzt habe ich eine Funktion, deren Implementierung (fast) auf beide Schnittstellen passt. Ich möchte, dass es so etwas macht:

public int foo((A | B) p) {
    int ret = 0;
    if (p instanceof B) {
        ret = p.somthingelse();
    }
    return ret + p.get();
}

Ich möchte keine Inspektion verwenden, da sich diese Funktion in der Hauptpipeline meines Programms befindet. Ich möchte, dass es eine gute Leistung hat. Ist das in Java möglich?

Bearbeiten:

Eine einfache Lösung besteht darin, foo()sie für jede Schnittstelle anders zu kopieren / einzufügen und zu implementieren. Aber in Wirklichkeit sind foo()die Schnittstellen viel länger und ich versuche, Codeduplikationen zu vermeiden.

Liran Funaro
quelle
2
A und B haben nichts gemeinsam. Erstellen Sie also zwei foo () -Methoden: eine mit einem B und eine mit einem A. Nehmen Sie auch keine Annahmen über die Ursache eines Leistungsproblems vor, bis Sie ein Leistungsproblem haben und diese Annahme durch Messungen überprüft haben .
JB Nizet
@JBNizet A und B haben get()gemeinsam. Dies ist natürlich ein vereinfachtes Beispiel. In Wirklichkeit haben sie mehr Funktionen gemeinsam.
Liran Funaro
1
Nein, das tun sie nicht. B erweitert A nicht. Sie haben also zufällig eine Methode mit derselben Signatur und demselben Rückgabetyp. Wenn jedes B ein A sein soll, sollte B A verlängern.
JB Nizet
1
@ user2478398 Hast du dir die Antwort von ernest_k angesehen? Ich denke, es ist das, worüber Sie sprechen.
Liran Funaro
1
@LiranFunaro Wenn sie (beide Schnittstellen) etwas gemeinsam haben, warum nicht zu einem Elternteil extrahieren und weiter erweitern? Nebenbei: publicAccessor ist für Schnittstellenmethoden redundant. Und meintest du instanceofstatt implements?
Naman

Antworten:

4

Es gibt leider keine Möglichkeit, eine solche rückwirkende Beziehung zwischen zwei nicht verwandten Typen herzustellen.

Wenn Sie Änderungen foo()und deren Aufrufe vornehmen können, können Sie möglicherweise funktionale Schnittstellen verwenden, die mit den Signaturen übereinstimmen, die Sie im Inneren aufrufen foo. Ich verwende IntSupplierhier mit entsprechenden Lambda-Ausdrücken konkrete Implementierungen von AundB .

Angenommen, Sie haben diese Implementierungen:

class AImpl implements A {
     //implementation
}
class BImpl implements B {
     //implementation
}

Sie können foozu etwas wechseln wie:

public int foo(IntSupplier get, IntSupplier somethingElse) {
    int ret = 0;
    if (somethingElse != null) {
        ret = somethingElse.getAsInt();
    }
    return ret + get.getAsInt();
}

Und nenne es so:

A a = new AImpl();
B b = new BImpl();

int result = this.foo(a::get, b::somethingelse);
ernest_k
quelle
Interessante Lösung! Dies kann auf eine Lösung erweitert werden, die keine Änderungen an der Signatur von foo erfordert. Schreiben Sie einfach zwei Implementierungen foo()für jede Schnittstelle und rufen Sie dann foo()mit passenden Lieferanten an. Möchten Sie dies hinzufügen oder möchten Sie, dass ich Ihre Antwort bearbeite?
Liran Funaro
1
@LiranFunaro Richtig. Ich war durch die Einschränkung eingeschränkt, dass die ifLogik im Inneren bleiben muss foo(). Aber ja, das können Sie definitiv tun, wenn es angebracht ist.
ernest_k
1
Entschuldigung, aber es gibt sehr wenig, was ich IntSupplierin dieser Antwort auf die Frage und den Vorschlag beziehen kann . Darüber hinaus kann das BImpl(oder einfach B) nicht als FunctionalInterface verwendet werden, wenn mehr als eine abstrakte Methode vorhanden ist. Aus einem Kommentar geht hervor , dass die Aussage " zwei nicht verwandte Typen " nicht zutrifft. Nitpick: IntSupplier getwird in Ihrem Code nicht verwendet.
Naman
1
@Naman Danke für deine Ansicht. Bwird nicht als funktionale Schnittstelle verwendet. b:somethingelseist IntSupplierin diesem Fall eine Methodenreferenz (ich würde vorschlagen, dass Sie den Code kompilieren). Und ja, Aund es Bgibt zwei völlig unabhängige Typen. Und was die Lösung insgesamt betrifft, denke ich, dass Sie es etwas umständlich finden. Ich kann annehmen, dass es feinere Ansätze gibt, aber nicht viele, die das Refactoring meiden, das sich das OP nicht leisten kann.
ernest_k
3

Ich kann mir nur Folgendes vorstellen:

public int foo(A p) {
    return internal_foo(p);
}

public int foo(B p) {
    return internal_foo(p);
}

private int internal_foo(Object p) {
    if (p instanceof A) {
        return ((A)p).get();
    }
    if (p instanceof B) {
        B b = (B)p;
        ret = b.somthingelse();
        return ret + b.get();
    }
    throw new ClassCastException("Wrong type");
}
Zefick
quelle
Vielen Dank. Das Problem dabei ist, dass die Funktionsimplementierung viel länger ist. Also muss ich viel Code kopieren / einfügen, was ich zu vermeiden versuche :)
Liran Funaro
2

Afaik, Java unterstützt dies nicht direkt, aber Sie können manuell einen "Type-Union-Wrapper" erstellen. Etwas wie das:

abstract class AorB {
    public static AorB wrap(A a) {
        return new AWrapper(a);
    }

    public static AorB wrap(B b) {
        return new BWrapper(b);
    }

    abstract int get();
    abstract int somethingElse();
}

class AWrapper extends AorB {
    private final A a;

    AWrapper(A a) {
        this.a = a;
    }

    @Override
    int get() {
        return a.get();
    }

    @Override
    int somethingElse() {
        return 0;
    }
}

class BWrapper extends AorB {
    private final B b;

    BWrapper(B b) {
        this.b = b;
    }

    @Override
    int get() {
        return b.get();
    }

    @Override
    int somethingElse() {
        return b.somethingElse();
    }
}

// and then

public int foo(A a) {
    return fooImpl(AorB.wrap(a));
}

public int foo(B b) {
    return fooImpl(AorB.wrap(b));
}

int fooImpl(AorB p) {
    return p.get() + p.somethingElse();
}

Sie können auch einige Inspektionsfunktionen hinzufügen hasSomethingElse(), z. B. wenn kein geeigneter Standardwert zurückgegeben werden soll, wie 0oben.

xs0
quelle
Ich würde diese Antwort auch akzeptieren, wenn ich zwei Antworten akzeptieren könnte.
Liran Funaro