2015-05-21 18 views
51

Ich habe meinen Kopf gegen diesen eine Weile geschlagen und dachte, dass vielleicht einige frische Augen das Problem sehen werden; vielen Dank für Ihre Zeit.Java Generics Puzzler, Erweiterung einer Klasse und Verwendung von Platzhaltern

import java.util.*; 

class Tbin<T> extends ArrayList<T> {} 
class TbinList<T> extends ArrayList<Tbin<T>> {} 

class Base {} 
class Derived extends Base {} 

public class Test { 
    public static void main(String[] args) { 
    ArrayList<Tbin<? extends Base>> test = new ArrayList<>(); 
    test.add(new Tbin<Derived>()); 

    TbinList<? extends Base> test2 = new TbinList<>(); 
    test2.add(new Tbin<Derived>()); 
    } 
} 

Mit Java 8. Es mir in test wie die direkten Erzeugung des Behälters sieht entsprechen den Behälter in test2, aber der Compiler sagt:

Test.java:15: error: no suitable method found for add(Tbin<Derived>) 
    test2.add(new Tbin<Derived>()); 
     ^

Wie schreibe ich Tbin und TbinList so die letzte Zeile ist akzeptabel?

Beachten Sie, dass ich tatsächlich getippt Tbin s hinzufügen werde, weshalb ich Tbin<Derived> in der letzten Zeile angegeben habe.

+0

In Eclipse ist die Fehlermeldung 'Die Methode hinzufügen (Tbin ) im Typ ArrayList > gilt nicht für die Argumente (Tbin ) '. – dimo414

+0

Versuchen Sie diese 'ArrayList > test3 = new TbinList <>(); '. Sehr interessant in der Tat. – Radiodef

+0

@Radiodef Eclipse zeigt einen Fehler an, der besagt, dass "Argumente für TbinList <>" nicht abgeleitet werden können. – TNT

Antwort

1

OK, hier ist die Antwort:

import java.util.*; 

class Tbin<T> extends ArrayList<T> {} 
class TbinList<T> extends ArrayList<Tbin<? extends T>> {} 

class Base {} 
class Derived extends Base {} 

public class Test { 
    public static void main(String[] args) { 

    TbinList<Base> test3 = new TbinList<>(); 
    test3.add(new Tbin<Derived>()); 

    } 
} 

Wie ich erwartet hatte, ziemlich offensichtlich, sobald ich es sah. Aber eine Menge herumstrampeln um hierher zu kommen. Java-Generika scheinen einfach zu sein, wenn Sie nur den Arbeitscode betrachten.

Danke, jeder, für ein Resonanzboden.

+0

Wenn Sie dies aus der Antwort von Tynn erhalten, sollten Sie in Betracht ziehen, es zu akzeptieren. http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – Radiodef

+0

Ich tat es nicht. Schau dir das Timing an. – user1677663

+2

Es zeigt die erste Antwort von tynn an meinem Ende. Wie auch immer, es ist nur eine routinemäßige Bemerkung. Manchmal werden Benutzer umstellen, weil sie nichts über das Akzeptieren wissen. – Radiodef

3

Sie eine beschränkte Platzhalter verwenden (TbinList<? extends Base>> ...). Mit diesem Platzhalter können Sie keine Elemente zur Liste hinzufügen. Wenn Sie weitere Informationen wünschen, finden Sie in der Dokumentation den Abschnitt über Wildcards.

+0

Ihr Link ist kaputt. –

+0

seltsam, einfach kopieren eingefügt. Gib mir eine Sekunde, ich werde es beheben – Paul

+0

Das behebt es, Danke! –

1

Sie können keine Objekte zu TbinList<? extends Base> hinzufügen, es ist nicht garantiert, welche Objekte Sie in die Liste einfügen. Es soll Daten von test2 lesen, wenn Sie Platzhalter extends

verwenden Wenn Sie als TbinList<? extends Base> erklärt, die Sie bedeutet, dass es jede Unterklasse der Klasse Basis oder class Base selbst, und wenn Sie es initialisieren Sie verwenden Diamant andere als konkrete Klasse Name, macht es Ihre test2 nicht offensichtlich, was es schwieriger zu sagen, welche Objekte eingefügt werden können. Mein Vorschlag ist, dass eine solche Deklaration zu vermeiden, es ist gefährlich, es kann nicht kompilieren Fehler, aber es ist schrecklich Code, könnten Sie etwas hinzufügen, aber Sie könnten auch die FALSCHE Sache hinzufügen, die Ihren Code brechen wird.

+1

Sie können, eigentlich. 'test2.add (new Tbin <>());' kompiliert gut und fügt eindeutig eine leere Liste hinzu, wenn ich 'test2' vorher und nachher drucke. – azurefrog

+0

Wenn ich sage "Sie kann nicht" ich meine, es ist gefährlich, unklare Art von Objekten hinzuzufügen :) Ich denke, ich sollte verwenden "Sie besser nicht" – haifzhan

1

Sie können die generische Typen wie folgt definieren:

class Tbin<T> extends ArrayList<T> {} 
class TbinList<K, T extends Tbin<K>> extends ArrayList<T> {} 

Dann würden Sie zB wie erstellen:

TbinList<? extends Base, Tbin<? extends Base>> test2 = new TbinList<>(); 
test2.add(new Tbin<Derived>()); 
10

Ersetzen der Definition von TbinList mit

class TbinList<T> extends ArrayList<Tbin<? extends T>> {} 

und definieren test2 mit

TbinList<Base> test2 = new TbinList<>(); 

stattdessen würde das Problem lösen.

Mit Ihrer Definition enden Sie mit einer ArrayList<Tbin<T>> wo T ist eine feste Klasse erweitert Base.

+0

Dies ist vernünftig, vorausgesetzt, sie brauchen nie nur z. eine 'TbinList', die äquivalent zu einer' ArrayList > 'ist. – Radiodef

37

Dies geschieht, weil der Weg capture conversion Werke:

There exists a capture conversion from a parameterized type G<T1,...,Tn> to a parameterized type G<S1,...,Sn> , where, for 1 ≤ i ≤ n :

  • If Ti is a wildcard type argument of the form ? extends Bi , then Si is a fresh type variable [...].

Capture conversion is not applied recursively.

Hinweis das Ende Bit. Also, was bedeutet, dass ein Typ wie folgt angegeben:

Map<?, List<?>> 
//  │ │ └ no capture (not applied recursively) 
//  │ └ T2 is not a wildcard 
//  └ T1 is a wildcard 

Nur „außen“ Platzhalter erfasst werden. Der Map Schlüssel-Platzhalter wird erfasst, aber der List Element-Platzhalter ist nicht. Aus diesem Grund können wir zum Beispiel eine List<List<?>>, aber keine List<?> hinzufügen. Die Platzierung der Wildcard ist wichtig.

diese über TbinList Tragen, wenn wir eine ArrayList<Tbin<?>> haben, ist der Platzhalter in einem Ort, wo es nicht erfasst wird, aber wenn wir eine TbinList<?> haben, ist der Platzhalter in einem Ort, wo sie gefangen genommen wird.

Als ich in den Kommentaren erwähnt, ist ein sehr interessanter Test dies:

ArrayList<Tbin<? extends Base>> test3 = new TbinList<>(); 

Wir werden diese Fehlermeldung erhalten:

error: incompatible types: cannot infer type arguments for TbinList<> 
    ArrayList<Tbin<? extends Base>> test3 = new TbinList<>(); 
                 ^
    reason: no instance(s) of type variable(s) T exist so that TbinList<T> conforms to ArrayList<Tbin<? extends Base>>

So gibt es keine Möglichkeit, damit es funktioniert, wie sie ist. Eine der Klassendeklarationen muss geändert werden.


Denken Sie darüber hinaus auf diese Weise nach.

Angenommen, wir hatten:

class Derived1 extends Base {} 
class Derived2 extends Base {} 

Und da eine Wildcard erlaubt Subtyping, wir können dies tun:

TbinList<? extends Base> test4 = new TbinList<Derived1>(); 

Sollten wir in der Lage sein, eine Tbin<Derived2>-test4 hinzufügen? Nein, das wäre Haufenverschmutzung. Wir könnten am Ende mit Derived2 s in einer TbinList<Derived1> schweben.

+0

Meine [Antwort] (http://StackOverflow.com/A/30385058/1129332) bricht Ihre Aussage, mit einem Trick der Methode zu helfen, den Haken zu verhindern. –

+0

@Ilya_Gazman Ihre Antwort verwendet eine ungeprüfte Besetzung, und es funktioniert nicht für eine 'TbinList '. – Radiodef

+0

Ich bearbeite es jetzt. Die Idee ist dieselbe. Warum ist das unkontrollierte Casting in diesem Fall schlecht? –