2016-02-28 5 views
30

Betrachten Sie das Snippet:Welcher Typ Sicherheit hätte verloren, hätten Generics die Untertypisierung unterstützt?

Number[] numbers = {1, 2.3, 4.5f, 6000000000000000000L}; 

Es ist durchaus in Ordnung ist, die oben genannten zu tun, Number eine abstrakte Klasse ist.

voran gehen,

List<Long> listLong = new ArrayList<Long>(); 
listLong.add(Long.valueOf(10)); 

List<Number> listNumbers = listLong; // compiler error - LINE 3 

listNumbers.add(Double.valueOf(1.23)); 

Hatte 3 Line wurde, erfolgreich kompiliert werden entworfen, wir würden mit einem List von Number s am Ende, das heißt,

for(Number num: listNumbers){ 
    System.out.println(num); 
} 

// 10 
// 1.23 

die alle Zahlen.

Ich kam in diese in einem Buch,

Generics nicht Subtypisierung unterstützt, weil es Probleme in Erreichen Typsicherheit führen wird. Deshalb List<T> nicht als Subtyp von List<S> betrachtet, wo S ist der Super-Typ T

Welche Art Sicherheit in diesem spezifischen Fall verloren hätte, wie oben erläutert, waren die Linie 3 war erfolgreich kompiliert werden?

+1

Lesen Sie diesen Artikel darüber, warum Java [Generika sind nicht kovariant] (http://www.ibm.com/developerworks/library/j-jtp01255/). – Ralf

+3

'Liste 'würde funktionieren. Es verhindert 'add'. –

Antwort

41
List<Long> listLong = new ArrayList<Long>(); 
List<Number> listNumbers = listLong; 

So listNumbers und listLong würden zwei Verweise auf die gleiche Liste sein, wenn das möglich ist, nicht wahr?

listNumbers.add(Double.valueOf(1.23)); 

Sie könnten also ein Double zu dieser Liste hinzufügen. listLong, vom Typ List<Long>, würde somit ein Double enthalten. Die Typensicherheit wäre somit gebrochen.

+1

Wie kann ich heute Morgen so dumm sein? –

+12

@ShirgillFarhanAnsari du bist nicht, es ist eigentlich eine gute Frage :) – Maroun

+0

@ShirgillFarhanAnsari In C# erstellt sie 'in' und' out' (Generic Modifier) ​​aus diesem Grund. Schauen Sie nach, wenn Sie eine Strategie kennenlernen möchten, die das in dieser Antwort aufgezeigte Problem vermeidet. – WorldSEnder

8

Wenn das der Fall wäre, könnten wir andere NumberlistNumbers Untertypen hinzufügen, die verboten sein müssen.

Stellen Sie sich vor, Sie fügen jetzt Objekte des Typs Double und ein, und später versuchen Sie, Long#reverse zu verwenden. Ihr Code wird kompilieren, aber wird natürlich bei Laufzeit (schlecht) die erste Double wird es durchkommen.

5

Lass sie ein Beispiel mit einer nicht-abstrakten Basisklasse verwenden:

public class Human { 
    public string getName() { 
     // ... 
    } 
} 

public class Student extends Human { 
    public void learn(Subject subject) { 
     // ... 
    } 
} 

public class Teacher extends Human { 
    public void teach(Subject subject) { 
     // ... 
    } 
} 

An jedem Ort, an dem ein Human erwartet wird, wird genauso gut ein Student oder Teacher tun, da sie vollständig den Human-Schnittstelle implementieren. (In diesem Fall kann das getName() für sie aufgerufen werden.) Java-Vererbung garantiert, dass dies technisch der Fall ist. Damit es semantisch funktioniert, ist der Job des Klassenautors, so dass sein Code die Liskov substitution principle erfüllt.

Also bedeutet das nicht, dass wir auch Collection<Teacher> ersetzen können, wo ein Collection<Human> erwartet wird? Nicht immer. Betrachten Sie die folgende Methode:

public class Human { 
    // ... 

    public void join(Set<Human> party) { 
     party.add(this); 
    } 
} 

Nun, wenn Java eine Set<Student> erlaubt als Partei übergeben werden, alle Versuche der nicht StudentHuman s, dass die Partei beizutreten würde zur Laufzeit zum Scheitern verurteilt.

In der Regel ist ein Container eines Subtyps ungeeignet, wenn der Empfänger (aufgerufen im Falle eines Funktionsarguments, Aufrufer im Falle eines Funktionsrückgabewerts) etwas hineinlegen will, aber akzeptabel wenn der Empfänger wollen nur Sachen rausnehmen und benutzen. Ein Container eines Supertyps ist ungeeignet, wenn der Empfänger Dinge entfernen und benutzen will, aber akzeptabel, wenn der Empfänger nur Sachen hineinlegt. Wenn der Empfänger also sowohl Material aus der Sammlung nimmt als auch Material in die Sammlung legt, müssen sie normalerweise eine Sammlung eines festen Typs benötigen.

Unsere join Methode setzt nur Human s in die party, so konnten wir auch eine Set<Object> oder eine nicht-generic Set oder äquivalent ein Set<?> ermöglichen. Java uns, das zu tun erlaubt mit lower-bounded wildcards:

public class Human { 
    // ... 

    public void join(Set<? super Human> party) { 
     party.add(this); 
    } 
} 

Für die Möglichkeiten in Richtung Unterklassen eröffnen, gibt es upper-bounded wildcards:

public class Teacher extends Human { 
    public void teach(Subject subject, Set<? extends Student> schoolClass) { 
     for (Student student : class) { 
      student.learn(subject); 
     } 
    } 
} 

Nun, wenn wir jemals Student Unterklasse, die übergebene schoolClass kann ein Set von seinem dieser Subtyp auch.

+0

Schöne Erklärung. – Maroun

3

Das Konzept, auf das Sie sich beziehen, ist variance.

Mit anderen Worten, wenn S ein übergeordneter Typ von T ist, ist List<S> ein Subtyp, geordneter Typ, gleich Typ oder unreleted zu List<T>?

Die Antwort für List - und alle anderen Java-Generics * - ist "nicht verwandt", d. H. Invariant.

class SuperType {} 

class Type extends SuperType {} 

class SubType extends Type {} 

List<Type> list = ... 

List<SuperType> superList = list; 
superList.add(new SuperType()); 
// no, we shouldn't be able to add a SuperType to list 

List<SubType> subList = list; 
SubType item = subList.get(0); 
// no, there's not necessarily only SubType items in list 

* Java tut den Begriff "use-site" Varianz haben, mit wildcards (?). Dies wird einschränken, welche Methoden aufgerufen werden können.

List<Type> list = ... 

List<? super SubType> wildcardList = list; 
wildcardList.add(new SubType()); 
// but...everything we get() is an Object 

oder

List<Type> list = ... 

List<? extends SuperType> wildcardList = list; 
SuperType item = wildcard.get(0); 
// but...it's impossible to add() 

FYI, haben einige Sprachen den Begriff der Definition-Website Varianz, z.B. Scala. So ist List[Int] tatsächlich ein Subtyp von List[Number]. Das ist möglich mit unveränderlichen Sammlungen (wiederum eine begrenzte Menge von Methoden), aber offensichtlich nicht für veränderbare.