2010-12-02 14 views
10

Warum hat die ErklärungWie funktionieren geschachtelte Typenargumente?

Set<Set<String>> var = new HashSet<Set<String>>(); 

Arbeit, aber die Erklärung

Set<Set<String>> var = new HashSet<HashSet<String>>(); 

Drossel?

Ich bin mir bewusst, dass "Top-Level" (nicht sicher, ob das der richtige Satz hier ist) Generika in einer Erklärung spielen nach anderen Regeln als die innerhalb der spitzen Klammern, aber ich bin interessiert, den Grund zu erfahren. Keine einfache Frage zu Google, also dachte ich, ich würde es versuchen.

Antwort

12

Es ist, weil Sie das Typsystem umgehen könnten, wenn es erlaubt wäre. Die Eigenschaft, die Sie wünschen, heißt covariance. Wenn Sammlungen covariant waren dann würden Sie in der Lage sein, dies zu tun:

Set<Set<String>> var = new HashSet<HashSet<String>>(); 

var.add(new TreeSet<String>()); 

A TreeSet ist eine Art von Set, und so statische Typprüfung würden Sie nicht von Einfügen eines TreeSet in var verhindern. Aber var erwartet nur HashSets und HashSets, nicht irgendeinen alten Set-Typ.

Ich persönlich schreibe immer Ihre erste Erklärung:

Set<Set<String>> var = new HashSet<Set<String>>(); 

Die äußere Klasse braucht eine conrete Implementierung haben, aber es gibt in der Regel keine Notwendigkeit, die innere Klasse festzunageln speziell auf Hashset. Wenn Sie ein Hash Set von Sets erstellen, können Sie loslegen. Ob Sie dann fortfahren, eine Reihe von HashSets in var einzufügen, können Sie später im Programm auswählen, ohne die Deklaration einschränken zu müssen.


Für was es wert ist, Arrays in Java sind covariant, im Gegensatz zu den Sammelklassen. Dieser Code wird kompiliert und löst eine Laufzeitausnahmebedingung aus, anstatt zur Kompilierungszeit abgefangen zu werden.

// Arrays are covariant, assignment is permitted. 
Object[] array = new String[] {"foo", "bar"}; 

// Runtime exception, can't convert Integer to String. 
array[0] = new Integer(5); 
3

Das ist wegen der Art, wie die Generika in Java arbeiten.

Set<? extends Set<String>> var = new HashSet<HashSet<String>>(); 
+3

Diese Antwort macht nicht viel Sinn für mich. John Kugelman und ColinD illustrieren das Problem sehr deutlich. – erickson

+2

Dies hat nichts damit zu tun, dass der Typparameter "abgeleitet" wird. – ColinD

+0

@ColinD: Richtig. Fest. – haylem

0

Der Unterschied ist einfach, durch Set<Set<String>> var = new HashSet<Set<String>> so dass Sie ermöglicht var nur einen Wert von Set<String> Typ zu akzeptieren (davon HashSet ist von Set).

Was Set<Set<String>> var = new HashSet<HashSet<String>>();, es wird nicht nur nicht kompilieren, da die innere Art von varSet<String> erwartet, aber es findet ein HashSet<String>. Dies würde bedeuten, dass var.add(new TreeSet<String>()); irrtümlich wäre (Typinkompatibilität zwischen HashSet und TreeSet).

Hoffe, das hilft.

+2

'Set > 'bedeutet, dass die äußere Menge nichts außer 'null' akzeptieren kann, aber es enthält Dinge, die ein' Set 'sind. – ColinD

+0

Und, ein 'Set >' erwartet (wenn durch "erwartet" du meine "die' add() 'Methode * erlaubt *") jeden Implementator von 'Set '. Set ist nicht einmal eine konkrete Klasse, also wäre es ziemlich nutzlos, wenn ein 'Set >' keine Subtypen von 'Set ' nehmen würde. –

+0

@ColinD & Mark Peters, notiert .... Ich habe meinen Beitrag aktualisiert. –

7

Der Grund ist, dass Set<Set<String>> nicht Set<HashSet<String>> entspricht! A Set<Set<String>> kann beliebig Typ Set<String> enthalten, während Set<HashSet<String>> nur HashSet<String> enthalten darf.

Wenn Set<Set<String>> set = new HashSet<HashSet<String>>() legal wäre, könnten Sie dies auch ohne Fehler:

Set<HashSet<String>> setOfHashSets = new HashSet<HashSet<String>>(); 
Set<Set<String>> set = setOfHashSets; 
set.add(new TreeSet<String>()); 
HashSet<String> wrong = set.iterator().next(); // ERROR! 

Es ist jedoch legal hier eine beschränkte Platzhalter zu verwenden:

Set<? extends Set<String>> set = setOfHashSets; 

Dies liegt daran, jetzt erlaubt , die Art des Objekts, das der Satz enthält, ist ? extends Set<String> ... mit anderen Worten, "einige spezifische aber unbekannte Klasse, die Set<String> implementiert". Da Sie nicht genau wissen, welche Art von Set<String> es enthalten darf, dürfen Sie nichts hinzufügen (außer null) ... Sie könnten sich irren, was später zu einem Fehler führt, wie in meinem erstes Beispiel.

Edit:

Beachten Sie, dass die „top level“ Generika Sie in Ihrer Frage beziehen sich auf parametrisierte Typen genannt werden, Typen bedeutet, dass man nehmen oder mehr Typ Parameter. Der Grund dafür, dass Set<Set<String>> set = new HashSet<Set<String>>() legal ist, ist, dass HashSet<T> implementiert und daher ein Subtyp von Set<T> ist. Beachten Sie jedoch, dass der Typparameter T übereinstimmen muss! Wenn Sie einen anderen Typ S haben, der ein Subtyp von T ist, ist ein HashSet<S> (oder nur ein Set<S> even) kein Untertyp von Set<T>! Ich habe den Grund dafür oben erklärt.

Dies ist genau die Situation hier.

Set<Set<String>> set = new HashSet<Set<String>>(); 

Wenn wir Set<String> mit T hier ersetzen, erhalten wir Set<T> set = new HashSet<T>(). Es ist leicht zu sehen, dass die tatsächlichen Argumente übereinstimmen und dass der Typ auf der rechten Seite der Zuweisung ein Subtyp des Typs auf der linken Seite ist.

Set<Set<String>> set = new HashSet<HashSet<String>>(); 

Hier haben wir Set<String> und HashSet<String> mit T und S bzw. zu ersetzen, wo S ein Subtyp von T ist. Mit diesem Ergebnis erhalten wir Set<T> set = new HashSet<S>(). Wie ich oben beschrieben habe, ist HashSet<S> kein Untertyp von Set<T>, daher ist die Zuweisung illegal.

0

Lässt Ihr Beispiel zu etwas einfacher

//Array style valid an Integer is a Number 
Number[] numArr = new Integer[10] 
//Generic style invalid 
List<Number> list1 = new ArrayList<Integer>(); 
//Compiled (erased) List valid, pre generics (java 1.4) 
List list2 = new ArrayList(); 

Die erste Zeile ist der Code mit covariant Arrays reduzieren, die Code legal java ist. Die nächste Zeile enthält ein einfaches Beispiel für Ihr Problem mit ungültigen Listen von Ganzzahl und Zahl. In der letzten Zeile haben wir die gültigen und gelöschten nicht generischen Listen.

Läßt ein Element in unsere Zahlen addieren, 1.5 wie eine vernünftige Zahl erscheint mir ^^

//this will compile but give us a nice RuntimeException 
numArr[0] = 1.5f 
//This would compile and thanks to type erasure 
//would even run without exception 
list1.add(1.5f) 

Runtime sollten nicht in gültigem Code passieren, aber numArr kann nur ganze Zahlen halten und nicht Zahlen als würde man glauben, . Generics fangen diesen Fehler zur Kompilierzeit ab, da sie nicht kovariant sind.

Und hier ist der Grund, warum diese Listen von Number und Integer nicht als gleich akzeptiert werden können. Die Methoden, die von beiden Listen bereitgestellt werden, akzeptieren unterschiedliche Argumente. Die Integer-Liste ist stärker eingeschränkt, wenn nur Ganzzahlen akzeptiert werden. Dies bedeutet, dass die von beiden Listen bereitgestellten Schnittstellen nicht kompatibel sind.

List<Number>.add(Number n) 
List<Integer>.add(Integer n) 

Das gleiche gilt für Ihre Sets

Set<Set<String>>.add(Set<String> s) 
HashSet<HashSet<String>>.add(HashSet<String> s) 

Das Add und andere Methoden beiden Typen gültig sind nicht kompatibel.

(zweite auf Antwort versuchen, ich hoffe jemand diesmal nicht mindread)

Verwandte Themen