7

为什么Bar.goOKwith argumentf2而不是 with argument f1

public class HelloWorld {
    public static void main(String[] args) {
        Foo<Foo<?>> f1 = new Foo<Foo<?>>();
        Foo<Foo<String>> f2 = new Foo<Foo<String>>();
        Bar.go(f1);     // not OK
        Bar.go(f2);     // OK
    }

    public static void p(Object o) {
        System.out.println(o);
    }
}

class Foo<E> {
}

class Bar {
    public static <T> void go(Foo<Foo<T>> f) {
    }
}

编译器不应该在这两种情况下自动推断类型Tcapture of ?

4

4 回答 4

2
Foo<Foo<?>> f1 = new Foo<Foo<?>>();

这意味着类型是未知的,并且可以将任何类型的对象添加到Foo<Foo<?>>异构的对象中,并且编译器不能保证其中的所有对象Foo<Foo<?>>都是相同类型的。因此,它不能传递Bar.go给以有界类型作为参数的对象。

您可以改为将其声明为Foo<Foo<Object>> f1 = new Foo<Foo<Object>>();将其传递到Bar.go您明确提及所有内容的地方都是 type Object

于 2012-09-23T03:39:46.770 回答
2

好问题!

E(在下面的注释中,在like中定义一个泛型类Foo< E >,将“协变方法”定义为返回E不带任何参数 using的方法E,而将“逆变方法”定义为相反:接受类型的形式参数E但不'不返回涉及的类型E。[这些术语的真正定义更复杂,但暂时不要介意。])

在 的情况下,编译器T似乎正在尝试绑定,因为如果您这样做Objectf1

class Bar0 {
    public static < T > void go( Foo< Foo< ? extends T > > f ) {
        // can pass a Foo< T > to a contravariant method of f;
        // can use any result r of any covariant method of f,
        // but can't pass T to any contravariant method of r
    }
}

然后go(f1)工作,但现在go(f2)没有,因为即使Foo< String > <: Foo< ? extends String >,这并不意味着Foo< Foo< String > > <: Foo< Foo< ? extends String > >

以下是一些针对f1和编译的修改f2

class Bar1 {
    public static < T > void go( Foo< ? super Foo< T > > f ) {
        // can't properly type the results of any covariant method of f,
        // but we can pass a Foo< T > to any contravariant method of f
    }
}

class Bar2 {
    public static < T > void go( Foo< ? extends Foo< ? extends T > > f ) {
        // can't pass a Foo< T > to a contravariant method of f;
        // can use result r of any covariant method of f;
        // can't pass a T to a contravariant method of r;
        // can use result of covariant method of r
    }
}
于 2012-09-23T05:12:52.587 回答
0

不错的阅读多级通配符是什么意思?

例子:

Collection< Pair<String,Long> >        c1 = new ArrayList<Pair<String,Long>>();

Collection< Pair<String,Long> >        c2 = c1;  // fine
Collection< Pair<String,?> >           c3 = c1;  // error
Collection< ? extends Pair<String,?> > c4 = c1; // fine  

当然,我们可以将 a 分配Collection<Pair<String,Long>>给 a Collection<Pair<String,Long>>。这里没有什么令人惊讶的。

但是我们不能将 a 分配Collection<Pair<String,Long>>给 a Collection<Pair<String,?>>。参数化类型Collection<Pair<String,Long>>是 String 和 Long 对的同质集合;参数化类型Collection<Pair<String,?>>是字符串对和未知类型的异构集合。例如,异构Collection<Pair<String,?>>可以包含 aPair<String,Date>并且显然不属于 a Collection<Pair<String,Long>>。因此,不允许分配。

于 2012-09-23T03:45:25.820 回答
0

我将证明如果编译器允许Bar.go(f1);,类型系统(安全)将被破坏:

Java 语法允许您T用作类型来在go(). 类似的东西:T t = <something>

现在,让我们使用,ArrayList而不是Foo

然后我们有:

class HW {
 public static void main(String[] args) {
        ArrayList<ArrayList<?>> f1 = new ArrayList<ArrayList<?>>();
        go(f1);     // not OK
    }

    public static <T> void go(ArrayList<ArrayList<T>> f) {
    }
}

ArrayList<?>是 的超类型ArrayList<String>,它也是 的超类型ArrayList<Integer>,这意味着您可以在 中执行以下操作main

ArrayList<?> s = new ArrayList<String>();
f1.add(s);

ArrayList<?> i = new ArrayList<Integer>();
f1.add(i);

现在,让我们假设编译器允许您调用go()withf1作为参数。推断的选项T是:

  1. T = Object,但ArrayList<ArrayList<Object>>不是ArrayList<ArrayList<?>>因为ArrayList<Object>ArrayList<?> So that is not allowed 选项的类型不同。

  2. T = ?,那么我们就可以做到:

    public static <T> void go(ArrayList<ArrayList<T>> f) {
         ArrayList<T> alt1 = f.get(0); // ArrayList<String>
         T str = alt1.get(0);
         ArrayList<T> alt2 = f.get(1); // ArrayList<Integer>
         alt2.add(str); // We have added String to List<Integer>
        // ... type system broken
    }
    

go()要在这两种情况下工作,您必须这样做:

public static void go(ArrayList<? extends ArrayList<?>> f) {
}
于 2012-09-23T06:29:27.167 回答