Java Generics无法转换Type in Type

时间:2014-06-17 11:20:19

标签: java generics

我遇到了这个问题:

public class Test {

    static class TestType {}

    static class Pair<A,B>{}

    public static void main( String [] args ) throws Exception {

        Collection<? extends TestType> val = null;

        List<? extends TestType> single = 
            testSingle( val ); // OK

        Pair<String,List<? extends TestType>> pair = 
            testPair( val ); // ERROR

    }

    static <T extends TestType> List<T> testSingle( Collection<T> val ){
        return null;
    }

    static <T extends TestType> Pair<String,List<T>> testPair( Collection<T> val ){
        return null;
    }

}

为什么第一个工作,第二个工作?

错误消息ist:

Type mismatch: cannot convert from Test.Pair<String,List<capture#3-of ? extends Test.TestType>> to Test.Pair<String,List<? extends Test.TestType>>

编辑:

使用Braj的答案我设置此项以在不使用泛型类型T时清除问题:

public class Test {

    static class TestType {}
    static class Implementation extends TestType {}

    static class Pair<A,B>{}

    public static void main( String [] args ) throws Exception {

        Collection<Implementation> val = null;

        List<Implementation> sinle = testSingle( val ); // OK

        Pair<String,List<Implementation>> pair = testPair( val ); // OK
        Pair<String,List<Implementation>> pair2 = testPair2( val ); // ERROR
        Pair<String,List<Implementation>> pair3 = testPair3( val ); // ERROR
        Pair<String,? extends List<Implementation>> pair4 = testPair4( val ); // ERROR

        run( val );

    }

    private static void run( Collection<? extends TestType> val ){

        List<? extends TestType> single = testSingle( val ); // OK

        Pair<String,List<? extends TestType>> pair = testPair( val ); // ERROR
        Pair<String,List<? extends TestType>> pair2 = testPair2( val ); // ERROR
        Pair<String,List<? extends TestType>> pair3 = testPair3( val ); // OK
        Pair<String,? extends List<? extends TestType>> pair4 = testPair4( val ); // OK
        Pair<String,? extends List<? extends TestType>> pairX = testPair( val ); // OK
        //My-Name-Is
        @SuppressWarnings( "unchecked" )
        Pair<String,List<? extends TestType>> fixed = 
            (Pair<String,List<? extends TestType>>)
            (Pair<String,?>) testPair( val ); // OK but ugly and stupid(?)
    }

    private static <T extends TestType> List<T> testSingle( Collection<T> val ){
        return null;
    }

    private static <T extends TestType> Pair<String,List<T>> testPair( Collection<T> val ){
        return null;
    }

    // Braj1
    private static <T extends TestType> Pair<String, List<TestType>> testPair2(Collection<T> val) {
        return null;
    }

    // Braj2
    private static <T extends TestType> Pair<String, List<? extends TestType>> testPair3(Collection<T> val) {
        return null;
    }

    // Seelenvirtuose
    private static <T extends TestType> Pair<String, ? extends List<? extends TestType>> testPair4(Collection<T> val) {
        return null;
    }

    // This one works in the way I wanted.
    private static <T extends TestType> void runOK( Collection<T> val ){

        List<T> single = testSingle( val ); // OK

        Pair<String,List<T>> pair = testPair( val ); // OK
    }
}

EDIT2:我可以使用:

在run()中修复此问题
@SuppressWarnings( "unchecked" )
Pair<String,List<? extends TestType>> fixed = 
    (Pair<String,List<? extends TestType>>)
    (Pair<String,?>) testPair( val );

但这是相当丑陋和愚蠢的(?)。

EDIT3:我编辑了上面的内容,包括Seelenviruose的答案,它仍然变得更加怪异。

我仍然不知道为什么需要这样做......

EDIT4:最后让它工作没有丑陋的演员:如果我使用<T extends TestType> run(...),编译器不会抱怨。我在上面改了。

5 个答案:

答案 0 :(得分:3)

第二种方法调用的返回类型是

Pair<String,List<? extends TestType>>

为什么是返回类型?好吧,编译器从输入参数 val 中推断出类型为Collection<? extends TestType>的类型。

上限有界通配符是破坏泛型固有不变性的Java机制。有了这样的结构,以下是可能的:

Collection<? extends Number> c1 = new LinkedList<Integer>();
Collection<? extends Number> c2 = new HashSet<Double>();
注意,我介绍了两种多态性。一个用于集合类型,另一个用于类型变量。

如果没有上限带通配符,则以下构造是编译器错误:

Collection<Number> c3 = new LinkedList<Integer>(); // compiler error

为什么会出错?简单:如果您有Collection<Number>,您可以在其中添加Double对象。但这不允许进入LinkedList<Integer>。因此,错误。

现在,这个上边界通配符的缺点是,您无法将任何内容(null除外)添加到Collection<? extends Number>


剪切:如果你理解它到现在为止,其余部分应该很容易。


您现在在通用返回类型中引入了一个附加层。你没有返回一个东西的列表,你返回一对字符串和一些列表

因此,上限现在不能位于列表元素的T类型上。它必须放在列表本身!

使用以下方法声明

<T extends TestType> Pair<String, ? extends List<T>> testPair(Collection<T> val) { ... }

将允许以下呼叫:

Pair<String, ? extends List<? extends TestType>> pair = testPair(val);

但是......我怀疑这样一对可能有什么合理的。如果是这样,您必须重新考虑您的设计。

答案 1 :(得分:2)

  

为什么第一个工作,第二个不工作?

因为List<ASpecificSubTypeOfTestType>(第一种方法的返回)是List<? extends TestType>的子类型,但是Pair<String, List<ASpecificSubTypeOfTestType>>(第二种方法的返回)不是{{1}的子类型}。

让我们暂时将您的示例从Pair<String, List<? extends TestType>>更改为Pair,将List更改为TestType

Object

从技术角度来看,public class Test { public static void main( String [] args ) throws Exception { Collection<?> val = null; List<?> single = testSingle( val ); // OK List<List<?>> pair = testList( val ); // ERROR } static <T> List<T> testSingle( Collection<T> val ){ return null; } static <T> List<List<T>> testList( Collection<T> val ){ return null; } } List<SomeSpecificType>的子类型,但List<?>不是List<List<SomeSpecificType>>的子类型,原因与{{1}相同}}不是List<List<?>>的子类型 - 类型参数是不同的具体类型(一个是List<String>,另一个是List<Object>)。

为了更实际的推理,List<SomeSpecificType>会为某些List<?>返回testList。我们不知道List<List<T>>是什么,但我们知道它是某种具体类型。返回的此T是一个只能包含T的列表。如果S不是T,则它不能包含List<List<T>>,因为List<T>List<S>不是彼此的子类型。即使我们不知道T是什么,我们知道存在一些T的选择,并且所有元素列表必须将该T作为类型参数。

另一方面,您分配给它的类型List<S>是一个可以同时包含每种类型列表的列表。因此,您可以同时输入List<T>List<List<?>>等等,这样就可以了。无论您选择List<Integer>是什么,都不能使用List<String>执行此操作。

因此,这两种类型显然是不相容的。

可能出现什么问题?使用List<List<T>>引用,您可以将TList<List<?>>插入列表中。然后使用函数内的List<Integer>引用,您可以将所有元素提取为List<String>,而不是它们。所以这是不安全的。

您可能会说,如果我从未将内容放入List<List<T>>引用中该怎么办?这样安全吗?但如果是这种情况,则应使用List<T>List<List<?>>通配符使其成为使用者,因此除了List<? extends List<?>>之外,您无法向其中插入任何内容。它还使类型兼容(? extendsnull)的子类型。

故事的寓意是,更深层次的通配符并不意味着你的想法。

答案 2 :(得分:1)

尝试

private static <T extends TestType> Pair<String, List<TestType>> testPair(Collection<T> val) {...}

Pair<String, List<TestType>> pair =  testPair(val);

OR

private static <T extends TestType> Pair<String, List<? extends TestType>> testPair(Collection<T> val) {...}

Pair<String, List<? extends TestType>> pair =  testPair(val);

答案 3 :(得分:0)

我仔细阅读了答案并解释了为什么它不起作用,但我没有注意到如何解决它的任何提议。我通过使该方法成为通用方法在我的情况下解决了它。

public main(){    
  Map<String, List<Integer>> stuff = service.buildStuff();
  consumeStuff(stuff);
}

private <T extends Number> consumeStuff(Map<String, List<T>> stuff){
  ...
}

因此,方法consumeStuff()期望第二种类型的map是一个List,它具有扩展Number的类型(所以Integer或Double)。通过使用一个map(main()中的var stuff)和List的显式类型Integer意味着Java可以在调用泛型方法consumeStuff()时推断出类型。

在consumeStuff内部,我们可以将内部列表的内容视为数字。我们可以深入研究它,从列表中获取一个值并将其传递给接受类型Number的方法/构造函数。

答案 4 :(得分:-1)

与此同时,我发现了有关Wildcard-Capture的页面:http://docs.oracle.com/javase/tutorial/java/generics/capture.html

据我所知,问题是编译生成了不同的类型

  • 表示本地变量:Pair<String,List<? extends TestType>>
  • 和返回值:Pair<String,List<capture#3-of ? extends TestType>>

在保留原始方法签名的同时解决此问题的“修复”是使用<T extends TestType>参数化调用方法。对于示例,它看起来像这样:

public class Test {

    static class TestType {}

    static class Pair<A,B>{}

    public static <T extends TestType> void main( String [] args ) throws Exception {

        Collection<T> val = null;

        List<T> single = testSingle( val ); // OK

        Pair<String,List<T>> pair = testPair( val ); // OK

    }

    static <T extends TestType> List<T> testSingle( Collection<T> val ){
        return null;
    }

    static <T extends TestType> Pair<String,List<T>> testPair( Collection<T> val ){
        return null;
    }

}

(这是我第一次写这样的主要内容:)

这个强制编译器为变量和方法的返回类型使用exacly相同的类型。

我仍然认为仿制药在某种程度上“错误”并且在这方面无法预测。