Ich weiß, dass es alle möglichen kontraintuitiven Eigenschaften der generischen Typen von Java gibt. Hier ist eine, die ich nicht verstehe und von der ich hoffe, dass sie mir jemand erklären kann. Wenn Sie einen Typparameter für eine Klasse oder Schnittstelle angeben, können Sie ihn so binden, dass mehrere Schnittstellen mit implementiert werden müssen public class Foo<T extends InterfaceA & InterfaceB>
. Wenn Sie jedoch ein tatsächliches Objekt instanziieren, funktioniert dies nicht mehr. List<? extends InterfaceA>
ist in Ordnung, kann aber List<? extends InterfaceA & InterfaceB>
nicht kompiliert werden. Betrachten Sie das folgende vollständige Snippet:
import java.util.List;
public class Test {
static interface A {
public int getSomething();
}
static interface B {
public int getSomethingElse();
}
static class AandB implements A, B {
public int getSomething() { return 1; }
public int getSomethingElse() { return 2; }
}
// Notice the multiple bounds here. This works.
static class AandBList<T extends A & B> {
List<T> list;
public List<T> getList() { return list; }
}
public static void main(String [] args) {
AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
foo.getList().add(new AandB());
List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
// This last one fails to compile!
List<? extends A & B> foobar = new LinkedList<AandB>();
}
}
Es scheint, dass die Semantik von bar
genau definiert sein sollte - ich kann mir keinen Verlust an Typensicherheit vorstellen, indem ich eine Schnittmenge von zwei Typen anstelle von nur einem zulasse. Ich bin mir sicher, dass es eine Erklärung gibt. Weiß jemand was es ist?
quelle
&
Zeichen ist die Standardmethode, um mehrere Grenzen zu kennzeichnen. "class AandBList<T extends A & B>
" ist genau so, wie die Sprache funktioniert, obwohl ich damit einverstanden bin, dass es intuitiver wäre, sie zu verwenden,<T extends A, B>
um sie abzugleichenpublic interface A extends C, D
. Und in generischen Typgrenzen verwenden Sieextends
unabhängig davon, ob es sich um eine Schnittstelle oder eine Klasse handelt. Verwirrend, ich weiß, aber so ist es derzeit.Antworten:
Interessanterweise
java.lang.reflect.WildcardType
sieht die Schnittstelle so aus, als würde sie sowohl obere als auch untere Grenzen für ein Platzhalterargument unterstützen. und jede kann mehrere Grenzen enthaltenDies geht weit über das hinaus, was die Sprache zulässt. Es gibt einen versteckten Kommentar im Quellcode
// one or many? Up to language spec; currently only one, but this API // allows for generalization.
Der Autor der Schnittstelle scheint der Ansicht zu sein, dass dies eine zufällige Einschränkung ist.
Die vorgefertigte Antwort auf Ihre Frage lautet: Generika sind bereits zu kompliziert. Das Hinzufügen von mehr Komplexität könnte sich als letzter Strohhalm erweisen.
Damit ein Platzhalter mehrere Obergrenzen haben kann, muss die Spezifikation gescannt und sichergestellt werden, dass das gesamte System weiterhin funktioniert.
Ein Problem, das ich kenne, wäre die Typinferenz. Die aktuellen Inferenzregeln können einfach nicht mit Schnittpunkttypen umgehen. Es gibt keine Regel, um eine Einschränkung zu reduzieren
A&B << C
. Wenn wir es auf reduzierenJede Strominferenzmaschine muss einer umfassenden Überholung unterzogen werden, um eine solche Gabelung zu ermöglichen. Das eigentliche ernsthafte Problem ist jedoch, dass dies mehrere Lösungen ermöglicht, aber es gibt keine Rechtfertigung, einander vorzuziehen.
Inferenz ist jedoch für die Typensicherheit nicht wesentlich; In diesem Fall können wir einfach die Schlussfolgerung ablehnen und den Programmierer bitten, Typargumente explizit einzugeben. Daher ist die Schwierigkeit der Inferenz kein starkes Argument gegen Intercection-Typen.
quelle
java.lang.reflect.WildcardType
. Vielen Dank :)Aus der Java-Sprachspezifikation :
Warum wird dies nicht unterstützt? Ich denke, was solltest du mit so etwas machen? - Nehmen wir an, es wäre möglich:
Was soll es dann sein?
list.get(0);
Rückkehr? Es gibt keine Syntax, um einen Rückgabewert von zu erfassen
A & B
. Das Hinzufügen von etwas zu einer solchen Liste wäre ebenfalls nicht möglich, daher ist es im Grunde genommen nutzlos.quelle
A a = list.get(0);
undB b = list.get(0);
beide wären sicher. Ich denke, ich kann immer umgestalten, um eine gemeinsame Superschnittstelle zu erhalten.extend
diese Liste sowieso nicht einfügen können. und um zu bekommen, kann man einfach sagenA a = list.get(0)
oderB b = list.get(0)
. Ich glaube nicht, dass dies ein typentheoretisches Problem ist, es ist nur eine Java-Einschränkung. Auf der anderen Seite <? Super A & B> ist fast bedeutungslos.SomeClass<T extends A & B>
der eine zurückgebende Methode deklariertList<T>
, und diese Methode dann für eine Variable vom Typ aufrufenSomeClass<?>
. Also +1 für die JLS-Referenz, aber -1 für die Vermutung.list
könnten Elemente von J, K und L enthalten undlist.get(0)
sie zurückgeben. In der Java-Spezifikation wird tatsächlich korrekt darauf hingewiesen, dass es keine Möglichkeit gibt, dieses Konzept direkt auszudrücken, aber J, K und L verwenden es indirekt durch mehrfache Vererbung von Schnittstellen.Kein Problem ... deklarieren Sie einfach den Typ, den Sie in der Methodensignatur benötigen.
Dies kompiliert:
public static <T extends A & B> void main(String[] args) throws Exception { AandBList<AandB> foo = new AandBList<AandB>(); // This works fine! foo.getList().add(new AandB()); List<? extends A> bar = new LinkedList<AandB>(); // This is fine too List<T> foobar = new LinkedList<T>(); // This compiles! }
quelle
extends ReferenceType | super ReferenceType
Gute Frage. Ich brauchte eine Weile, um es herauszufinden.
Vereinfachen wir Ihren Fall: Sie versuchen dasselbe zu tun, als ob Sie eine Klasse deklarieren, die zwei Schnittstellen erweitert, und dann eine Variable, die diese beiden Schnittstellen als Typ hat, ungefähr so:
class MyClass implements Int1, Int2 { } Int1 & Int2 variable = new MyClass()
Natürlich illegal. Und das entspricht dem, was Sie mit Generika versuchen. Was Sie versuchen zu tun ist:
Um foobar zu verwenden, müssten Sie jedoch eine Variable beider Schnittstellen folgendermaßen verwenden :
A & B element = foobar.get(0);
Was in Java nicht legal ist . Dies bedeutet, dass Sie die Elemente der Liste gleichzeitig als zwei Typen deklarieren, und selbst wenn unser Gehirn damit umgehen kann, kann die Java-Sprache dies nicht.
quelle
A a = list.get(0);
undB b = list.get(0);
wäre gültig.class C<T extends A & B> { public T foo() {};}
wo Sie eine Variable mit mehreren Grenzen zurückgeben.list.get(0).*
?Für das, was es wert ist: Wenn sich jemand darüber wundert, weil er dies wirklich gerne in der Praxis anwenden möchte, habe ich es umgangen, indem ich eine Schnittstelle definiert habe, die die Vereinigung aller Methoden in allen Schnittstellen und Klassen enthält, mit denen ich arbeite. dh ich habe versucht, folgendes zu tun:
class A {} interface B {} List<? extends A & B> list;
was illegal ist - also habe ich stattdessen folgendes gemacht:
class A { <A methods> } interface B { <B methods> } interface C { <A methods> <B methods> } List<C> list;
Dies ist immer noch nicht so nützlich wie
List<? extends A implements B>
das Eingeben von etwas, wie wenn beispielsweise jemand Methoden zu A oder B hinzufügt oder entfernt, die Eingabe der Liste nicht automatisch aktualisiert wird, sondern eine manuelle Änderung von C erforderlich ist. Aber es funktioniert meine Bedürfnisse.quelle