2017-10-26 4 views
2

Warum Diamond-Operator beim Instanziieren generischer Outer-Klasse (zusammen mit Inner-Klasse) gibt Fehler in Snippet 2, während Snippet 1 völlig in Ordnung ist?Warum die Verwendung des Diamant-Operators in Outer fehlschlägt <String> .Inner obj2 = new Outer <>(). New Inner()? Während das gleiche mit Inner ist OK?

Ich weiß, dass seltene Arten verboten sind, aber mein Fall ist nicht seltene Arten - in seltenen Typen sind sowohl äußere als auch innere generisch, aber eine von ihnen (entweder eine) wird als roh, und die andere als generisch.

Schnipsel 1:

class Outer { 
    class Inner<T> { 

    } 
} 

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

     Outer.Inner obj1 = new Outer().new Inner<>(); // fine ! 
     Outer.Inner<String> obj2 = new Outer().new Inner<>(); // fine ! 

    } 
} 

Schnipsel 2:

class Outer<T> { 
    class Inner { 

    }  
} 

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

     Outer.Inner obj1 = new Outer<>().new Inner(); // fine ! 
     Outer<String>.Inner obj2 = new Outer<>().new Inner(); // error ! 

    } 
} 

P.S. Getestet auf Eclipse-Compiler.

+2

Ich bin mir nicht ganz sicher hier (hätte in der Spezifikation zu graben), aber ich nehme an, das ist wegen der Ausführungsreihenfolge und Typschlussfolgerung hier ein Problem. Was ich meine, ist, dass Typ-Inferenz den Typ von "obj2" als "Inner" und _maybe_ sogar als den äußeren Typ "Outer " sieht, aber da er zuerst eine Instanz von "Outer" erzeugen muss, die _nicht_ eine Instanz von 'Inner ist 'Schlußfolgerung schlägt fehl. Im Allgemeinen wurde die Art der Rückschlüsse über mehrere Codeebenen (kann auch Methodenaufrufe sein) mit jeder Java-Version verbessert, ist aber wahrscheinlich noch lange nicht perfekt. – Thomas

+1

WARUM würdest du diesen Code überhaupt schreiben? Wenn Sie sich für die Instantiierung der äußeren Klasse nicht interessieren, warum sollten Sie überhaupt eine innere Klasse benutzen? – EJP

+0

@EJP Ich verstehe nicht, wie Sie daraus geschlossen haben, dass der OP die Instanziierung der äußeren Klasse nicht interessiert. In "Outer .Inner obj2 = new Outer <>(). New Inner();" sieht es so aus, als wolle er speziell, dass die äußere Klasse ein "Outer " ist. – DodgyCodeException

Antwort

0

Compile Fehler:

Error:(11, 50) java: incompatible types: Outer< java.lang.Object >.Inner cannot be converted to Outer< java.lang.String >.Inner

was bedeutet, dass Sie ändern sollten:

Outer<String>.Inner obj2 = new Outer<>().new Inner() 

zu:

Outer<String>.Inner obj2 = new Outer<String>().new Inner() 

hält Der Compiler new Outer<>() als Ausgangstyp (Standard: Object), während es wird erwartet, Outer<String>, daher müssen wir genauer mit t sein, um es zu lösen Der generische Typ, den wir in der Aufgabe bereitstellen.

+0

Meine Kommentare sind nicht mehr anwendbar :) so entfernt – Smiley

2

Sie versuchen den Diamanten-Operator zu verwenden, um die Typ-Parameter abzuleiten:

Outer<String>.Inner obj2 = new Outer<>().new Inner(); // error ! 

Die JLS, section 15.9, sagt über Diamanten:

A class instance creation expression specifies a class to be instantiated, possibly followed by type arguments (§4.5.1) or a diamond (<>) if the class being instantiated is generic

Sie haben zwei Klassen Instanziierung Ausdrücke:

new Outer<>() 

und

new Inner() // or more precisely, new Outer<>().new Inner() 

Gegen Ende des Abschnitts 15.9, unterscheidet sie zwischen diesen zwei Ausdrücken:

A class instance creation expression is a poly expression (§15.2) if it uses the diamond form for type arguments to the class, and it appears in an assignment context or an invocation context (§5.2, §5.3). Otherwise, it is a standalone expression.

Somit wird der zweite Ausdruck ein Poly Ausdruck ist, was bedeutet, daß seine Aktivität auf dem Zuweisungskontext abhängt; aber der erste Ausdruck, new Outer<>(), ist ein eigenständiger Ausdruck: Sein Typ wird frei von irgendeinem Kontext ausgewertet.

Ihre andere Aussage

Outer.Inner obj2 = new Outer<>().new Inner(); 

ist in Ordnung, weil Sie Outer als Ausgangstyp verwendet werden.

+0

Dies erklärt nicht, warum die erste Zeile in Snippet 2 funktioniert. – Sweeper

+0

@Tom und Sweeper danke für die Rückmeldung. Du hast beide recht. Ich habe meine Antwort geändert. – DodgyCodeException

+0

Gute Arbeit. Danke für das Update. – Tom

1

Die Lösung, um den Code kompilieren zu lassen, wird in alfasins Antwort behandelt.

Der Grund dafür ist, wie Java Typen ableitet.Java kann in bestimmten Fällen nur auf einen Typ schließen (siehe Dokumentation für weitere Details here, speziell den Abschnitt Target Types).

Zitat aus dieser docs:

Note: It is important to note that the inference algorithm uses only invocation arguments, target types, and possibly an obvious expected return type to infer types. The inference algorithm does not use results from later in the program.

Das Problem mit dem Schnipsel kommt auf Typen durch Ziel Typinferenz und die Verwendung des Punkt-Operators zu Ketten Methoden abgeleitet. Wenn Sie den Punktoperator verwenden, wird das Ergebnis der ersten Methode zuerst für die zweite Methode verwendet (wird piped). Dies ändert also den Zieltyp, so dass es der Typ ist, der vom folgenden Teil nach dem Punkt erwartet wird. Es wird jedoch nur das Ergebnis der letzten Methode in der Kette von Methoden dem Variablentyp zugewiesen, und somit können die generischen Typen der letzten Methode in Ihrem Fall abgeleitet werden.

So im ersten Schnipsel, der Teil, wo der Typ abgeleitet werden muss, ist die letzte Methode nach dem Punkt-Operator, so dass der Zieltyp für diese Methode ist die Variable, auf die das Ergebnis zugewiesen werden soll und kann somit abgeleitet werden. d Vom ersten Schnipsel der Zeilen:

Outer.Inner<String> obj2 = new Outer().new Inner<>(); // fine ! 

Seit ‚neuen Outer()‘ ein Objekt zurückgibt, das nicht einen generischen Typen hat, braucht keine Folgerung auftreten. Die zweite Methode new Inner<>() kann fortgesetzt werden. Das Ergebnis der Methode new Inner<>() wird hier der Variablen obj2 zugewiesen, die den Typ Outer.Inner<String> hat, und daher kann von ihrem Ziel abgeleitet werden, dass TString sein soll.

Im zweiten Ausschnitt ist der Teil, bei dem der Typ abgeleitet werden muss, die Methode vor dem Punktoperator. Dies bedeutet, dass das Ergebnis der ersten Methode auf die zweite Methode angewendet wird.

So von Ihrem zweiten Schnipsel der Zeilen:

Outer<String>.Inner obj2 = new Outer<>().new Inner(); 

Der Zieltyp von new Outer<>() ist, was new Inner() erwartet, was Outer<T>.newInner() ist; Aufgrund von Type-Löschungen und der Funktionsweise von Java-Generics wird dieses T als Object angesehen, da es nicht als ein bestimmter Typ angegeben ist. Nun kann der zweite Teil fortgesetzt werden, aber das Ergebnis der zweiten Methode ist jetzt vom Typ Outer<Object>.Inner, der nicht in den zugewiesenen Typ der Variablen konvertiert werden kann.

Sie müssen also einen Typzeugen für die Methode angeben, d. H. new Outer<SomeMethod>(), da im zweiten Snippet kein geeigneter Zieltyp vorhanden ist, um die Typinterferenzen so ausführen zu können, wie Sie möchten.

+0

Beachten Sie, dass die Zeile "Outer.Inner obj1 = new Outer(). New Inner <>(); // fine! 'in Snippet 2 funktioniert, weil hier 'obj1' als unformatierter Typ deklariert ist und der unformatierte Typ den Compiler anweist, jegliche Typprüfung der generischen Variablen 'T' zu ignorieren. Raw-Typen sollten im Code vermieden werden, da sie viele Probleme verursachen können und nur rückwärtskompatibel mit Java 1.4 und früheren Versionen sind. – Smiley