2017-03-03 1 views
1

Hier ist der Code. Die Fehler und Warnungen für Java 7 und 8 sind in Kommentaren enthalten.Warum kompiliert dieses komplexe Generika-Beispiel nicht mit Java 7 (aber mit Java 8), und wie kann ich es umgehen?

Es ist möglich, die Unterschrift von takeCollection(...) zu ändern, so dass Anrufer eine Möglichkeit haben, es zu nennen und SubRoot/Foo/Bar Sammlungen übergeben und diese kompilieren sowohl gegen Java 7 und 8, die zwar nicht speziell in der Methodensignatur beziehen, die zu SubRoot/Foo/Bar Typen überladen (es kann viele von diesen geben, einige in abhängigen Projekten, die zu diesem nicht sichtbar sind)? Wenn das so ist, wie?

package generics; 

import java.util.Collection; 

// ------------------------------------------------------------------------------- 
// Cannot change this (start) 
//------------------------------------------------------------------------------- 
interface Root<F extends Foo<F,B>, B extends Bar<F,B>> {} 
class  Foo <F extends Foo<F,B>, B extends Bar<F,B>> implements Root<F,B> {} 
class  Bar <F extends Foo<F,B>, B extends Bar<F,B>> implements Root<F,B> {} 

interface SubRoot extends Root<SubFoo, SubBar> {} 
class  SubFoo extends Foo <SubFoo, SubBar> implements SubRoot {} 
class  SubBar extends Bar <SubFoo, SubBar> implements SubRoot {} 
//------------------------------------------------------------------------------- 
// End of cannot change block. 
//------------------------------------------------------------------------------- 

public class GenericsTest { 

    <R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>> 
    void takeOne(R one) {} 

    // The following method signature can be changed a bit, but it has to accept a collection 
    // of Roots, or ANY of its subtypes. 
    <R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>, C extends Collection<R>> 
    void takeCollection(C collection) {} 

    // ------------------------------------------ 
    // Test/illustration code that compiles well 
    // ------------------------------------------ 

    @SuppressWarnings("rawtypes") // Warnings understood 
    void testOneRoot1(Root root, Foo foo, Bar bar) { 
    takeOne(root); 
    // Java 1.7 and 1.8 agree about the warning, as expected. 
    // 
    // generics\GenericsTest.java:35: warning: [unchecked] unchecked method invocation: method takeOne in class GenericsTest is applied to given types 
    // takeOne(root); 
    //   ^
    // required: R 
    // found: Root 
    // where R,F,B are type-variables: 
    // R extends Root<F,B> declared in method <R,F,B>takeOne(R) 
    // F extends Foo<F,B> declared in method <R,F,B>takeOne(R) 
    // B extends Bar<F,B> declared in method <R,F,B>takeOne(R) 

    takeOne(foo); 
    // Java 1.7 and 1.8 agree about the warning, as expected. 
    // 
    // generics\GenericsTest.java:48: warning: [unchecked] unchecked method invocation: method takeOne in class GenericsTest is applied to given types 
    // takeOne(foo); 
    //   ^
    // required: R 
    // found: Foo 
    // where R,F,B are type-variables: 
    // R extends Root<F,B> declared in method <R,F,B>takeOne(R) 
    // F extends Foo<F,B> declared in method <R,F,B>takeOne(R) 
    // B extends Bar<F,B> declared in method <R,F,B>takeOne(R) 

    takeOne(bar); 
    // Java 1.7 and 1.8 agree about the warning, as expected. 
    // 
    // generics\GenericsTest.java:61: warning: [unchecked] unchecked method invocation: method takeOne in class GenericsTest is applied to given types 
    // takeOne(bar); 
    //   ^
    // required: R 
    // found: Bar 
    // where R,F,B are type-variables: 
    // R extends Root<F,B> declared in method <R,F,B>takeOne(R) 
    // F extends Foo<F,B> declared in method <R,F,B>takeOne(R) 
    // B extends Bar<F,B> declared in method <R,F,B>takeOne(R) 
    } 

    <R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>> 
    void testOneRoot2(R root, F foo, B bar) { 
    takeOne(root); // All fine 
    takeOne(foo); // All fine 
    takeOne(bar); // All fine 
    } 

    void testOneSubRoot(SubRoot subRoot, SubFoo subFoo, SubBar subBar) { 
    takeOne(subRoot); // All fine 
    takeOne(subFoo); // All fine 
    takeOne(subBar); // All fine 
    } 

    void testRootCollectionRaw(@SuppressWarnings("rawtypes") Collection<? extends Root> collection) { 
    takeCollection(collection); 
    // Java 1.7 quiet (not expected). Java 1.8 produces a warning, as expected: 
    // 
    // generics\GenericsTest.java:89: warning: [unchecked] unchecked method invocation: method takeCollection in class GenericsTest is applied to given types 
    //  takeCollection(collection); 
    //     ^
    // required: C 
    // found: Collection<CAP#1> 
    // where C,R,F,B are type-variables: 
    //  C extends Collection<R> declared in method <R,F,B,C>takeCollection(C) 
    //  R extends Root<F,B> declared in method <R,F,B,C>takeCollection(C) 
    //  F extends Foo<F,B> declared in method <R,F,B,C>takeCollection(C) 
    //  B extends Bar<F,B> declared in method <R,F,B,C>takeCollection(C) 
    // where CAP#1 is a fresh type-variable: 
    //  CAP#1 extends Root from capture of ? extends Root 
} 

    // -------------------------------------------- 
    // Test/illustration code that does NOT compile 
    // -------------------------------------------- 

    <R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>> 
    void testRootCollection1(Collection<? extends R> collection) { 
    takeCollection(collection); 
    // JDK 1.8 quiet (as expected). JDK 1.7 yields an error (not expected): 
    // 
    // generics\GenericsTest.java:112: error: invalid inferred types for R#1,F#1,B#1; inferred type does not conform to declared bound(s) 
    //  takeCollection(collection); 
    //     ^
    //  inferred: CAP#1 
    //  bound(s): Root<CAP#2,CAP#3> 
    // where R#1,F#1,B#1,C,R#2,F#2,B#2 are type-variables: 
    //  R#1 extends Root<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C) 
    //  F#1 extends Foo<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C) 
    //  B#1 extends Bar<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C) 
    //  C extends Collection<R#1> declared in method <R#1,F#1,B#1,C>takeCollection(C) 
    //  R#2 extends Root<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection1(Collection<? extends R#2>) 
    //  F#2 extends Foo<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection1(Collection<? extends R#2>) 
    //  B#2 extends Bar<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection1(Collection<? extends R#2>) 
    // where CAP#1,CAP#2,CAP#3 are fresh type-variables: 
    //  CAP#1 extends R#2 from capture of ? extends R#2 
    //  CAP#2 extends Foo<CAP#2,CAP#3> from capture of ? 
    //  CAP#3 extends Bar<CAP#2,CAP#3> from capture of ? 
    } 

    <R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>> 
    void testRootCollection2(Collection<R> collection) { 
    takeCollection(collection); 
    // JDK 1.8 quiet (as expected). JDK 1.7 yields an error (not expected): 
    // 
    // generics\GenericsTest.java:136: error: invalid inferred types for R#1,F#1,B#1; inferred type does not conform to declared bound(s) 
    //  takeCollection(collection); 
    //     ^
    //  inferred: R#2 
    //  bound(s): Root<CAP#1,CAP#2> 
    // where R#1,F#1,B#1,C,R#2,F#2,B#2 are type-variables: 
    //  R#1 extends Root<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C) 
    //  F#1 extends Foo<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C) 
    //  B#1 extends Bar<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C) 
    //  C extends Collection<R#1> declared in method <R#1,F#1,B#1,C>takeCollection(C) 
    //  R#2 extends Root<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection2(Collection<R#2>) 
    //  F#2 extends Foo<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection2(Collection<R#2>) 
    //  B#2 extends Bar<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection2(Collection<R#2>) 
    // where CAP#1,CAP#2 are fresh type-variables: 
    //  CAP#1 extends Foo<CAP#1,CAP#2> from capture of ? 
    //  CAP#2 extends Bar<CAP#1,CAP#2> from capture of ? 
    } 

    void testSubRootCollection1(Collection<SubRoot> collection) { 
    takeCollection(collection); 
    // JDK 1.8 quiet (as expected). JDK 1.7 yields an error (not expected): 
    // 
    // generics\GenericsTest.java:158: error: invalid inferred types for R,F,B; inferred type does not conform to declared bound(s) 
    //  takeCollection(collection); 
    //     ^
    //  inferred: SubRoot 
    //  bound(s): Root<CAP#1,CAP#2> 
    // where R,F,B,C are type-variables: 
    //  R extends Root<F,B> declared in method <R,F,B,C>takeCollection(C) 
    //  F extends Foo<F,B> declared in method <R,F,B,C>takeCollection(C) 
    //  B extends Bar<F,B> declared in method <R,F,B,C>takeCollection(C) 
    //  C extends Collection<R> declared in method <R,F,B,C>takeCollection(C) 
    // where CAP#1,CAP#2 are fresh type-variables: 
    //  CAP#1 extends Foo<CAP#1,CAP#2> from capture of ? 
    //  CAP#2 extends Bar<CAP#1,CAP#2> from capture of ? 
    } 

    void testSubRootCollection2(Collection<? extends SubRoot> collection) { 
    takeCollection(collection); 
    // JDK 1.8 quiet. JDK 1.7 yields an error: 
    // 
    // generics\GenericsTest.java:177: error: invalid inferred types for R,F,B; inferred type does not conform to declared bound(s) 
    //  takeCollection(collection); 
    //     ^
    //  inferred: CAP#1 
    //  bound(s): Root<CAP#2,CAP#3> 
    // where R,F,B,C are type-variables: 
    //  R extends Root<F,B> declared in method <R,F,B,C>takeCollection(C) 
    //  F extends Foo<F,B> declared in method <R,F,B,C>takeCollection(C) 
    //  B extends Bar<F,B> declared in method <R,F,B,C>takeCollection(C) 
    //  C extends Collection<R> declared in method <R,F,B,C>takeCollection(C) 
    // where CAP#1,CAP#2,CAP#3 are fresh type-variables: 
    //  CAP#1 extends SubRoot from capture of ? extends SubRoot 
    //  CAP#2 extends Foo<CAP#2,CAP#3> from capture of ? 
    //  CAP#3 extends Bar<CAP#2,CAP#3> from capture of ? 
    } 
} 
+5

Huch, das sind einige gruselig aussehende Generika. Könnten Sie es vielleicht abstellen, nur nach einer dieser Wände des Fehlertextes zu fragen, anstatt nach all diesen? –

+2

Ich denke auch, dass Sie ziemlich viel erwarten ... – GhostCat

+0

Die meisten Fehler sind die gleichen. Ich habe sie hier der Vollständigkeit halber hinzugefügt, da sich einige Leute darüber beschwert haben, dass sie sie nicht gesehen haben, als ich sie ursprünglich gefragt habe. Ich habe das Problem auf ein Minimum reduziert - die oberen 27 Zeilen sind alles, worauf es wirklich ankommt, der Rest illustriert nur das Problem auf verschiedene Arten. – Learner

Antwort

3

Alle der Kompilierungsfehlern gehen weg für mich, wenn ich die Unterschrift von takeCollection zu ändern:

<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>> 
void takeCollection(Collection<? extends R> collection) {} 

Es gibt, wenn auch nicht markiert/unsichere Operationen Warnungen, die alle im Zusammenhang mit Roh-Typen.

+0

Das funktioniert tatsächlich! Kann jemand erklären, warum ist das anders? – Learner

+1

"Das funktioniert tatsächlich!" Klingt nicht so überrascht. –

+0

:) Nun, ich habe viele Dinge ausprobiert, aber nicht diese Variante. Warum würde das etwas anderes bedeuten als ein Compiler? Ist nicht ungültig accept (P p) soll genauso wirken wie void accept (Q q)? – Learner

2

würde ich die Unterschriften ändern wie folgt:

void takeOne(Root<?,?> one) {} 

void takeCollection(Collection<? extends Root<?,?>> collection) {} 

void testOneRoot1(Root<?,?> root, Foo<?,?> foo, Bar<?,?> bar) {} 

<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>> 
void testOneRoot2(R root, F foo, B bar) {} 

void testOneSubRoot(SubRoot subRoot, SubFoo subFoo, SubBar subBar) {} 

@SuppressWarnings("unchecked") 
void testRootCollectionRaw(@SuppressWarnings("rawtypes") Collection<? extends Root> collection) { 
    takeCollection((Collection<? extends Root<?,?>>)collection);  
}  

void testRootCollection1(Collection<? extends Root<?,?>> collection) {} 

void testRootCollection2(Collection<? extends Root<?,?>> collection) {} 

Ihr Code kompilieren sollte mit diesem für beide Java-Signaturen 7 und Java 8 ohne Fehler oder Warnungen. Es gibt mehrere Stellen in Ihrem Code, an denen es sinnvoll ist, generische Typparameter durch Platzhalter zu ersetzen. Zum Beispiel ist <R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>> void takeOne(R one) {} fast gleichbedeutend mit void takeOne(Root<?,?> one) {}, denn wie zu binden F und B ist bereits durch die Signatur von Root definiert. testRootCollectionRaw ist ein Sonderfall, da er einen Rawtype als übergeordnet bezeichnet. Ich würde lieber diesen Fall mit einer unkontrollierten Besetzung in der Methode als durch die Anpassung der takeCollection Signatur für die Arbeit zu lösen.

Der Grund, warum Ihr ursprünglicher Code nicht für Java 7 kompiliert, ist, dass Typinferenz in Java 7 nicht so fortgeschritten ist wie in Java 8. Java 8 wird den Zieltyp eines Ausdrucks bei Typinferenz berücksichtigen. Im Allgemeinen ist es eine gute Idee, explizite Typparameter durch Platzhalter zu ersetzen, wo es möglich ist. Ein Nebeneffekt der Praxis ist, dass Java 7 in generischen Methoden leichter mit Platzhaltern umgehen kann als mit expliziten Typparametern.

+0

testRootCollectionRaw ist nur eine Illustration, ich interessiere mich nicht wirklich für Rohtypen. Wildcards sind jedoch nicht völlig gleichwertig. – Learner

Verwandte Themen