2012-09-23 29 views
7

爲什麼Bar.goOK帶有參數f2但不帶參數f1多級別通用類型的Java通配符

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) { 
    } 
} 

不應該編譯器自動推斷T類型,在兩種情況下capture of ?

回答

2

偉大的問題!

(在下面的評論,WRT一類通用的在EFoo<E>,定義「協變方法」,其不使用E具有任何參數返回一個E的方法,以及「逆變方法」爲相反:一個其需要一個E類型的形式參數,但不返回涉及E的類型[這些術語的真正定義比較複雜,但現在不用介意])

似乎編譯器試圖綁定TObjectf1的情況下,因爲如果你做

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 > >

這裏有兩個f1f2編譯了一些修改:

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 
    } 
} 
+0

很高興看到工作示例。 – Cyker

+0

'Foo >'的情況如何?如果我需要以'Foo '的方式工作,我可以寫'Foo <? '延伸Foo >'但我覺得它會帶來更多的差異,我實際上會打算如果我能夠使用'Foo >'。根據方差來調用'Foo '作爲'Foo '如何接受? – ony

+1

如果我理解你的問題@ony,它根本不接受它。 'Foo >'不是'Foo < Foo< ? >>'。一般而言,即使'S'是'T','G < S >'也不是'G < T >',而'G < S >'是'G <?擴展T>'和'G < T >'是'G < ? super S >'。 'Foo < ? >'是Foo <?的簡稱嗎?擴展Object>',所以'Foo < String >'是'Foo < ? >'。但讓'Foo < String >'爲'S'並且'Foo < ? >'爲'T'。上面的非關係意味着'Foo < S >'不是'Foo < T >'。 –

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

這意味着該類型是未知的,並且任何類型的對象可以被添加到Foo<Foo<?>>異類和編譯器不能保證在所有Foo<Foo<?>>對象是同一類型的。因此它不能傳遞給以有界類型爲參數的Bar.go

您可以改爲聲明Foo<Foo<Object>> f1 = new Foo<Foo<Object>>();將其傳遞給Bar.go,您明確提到所有類型都是Object

+0

實際上,假設'Foo'像一個集合,它是你的建議類型是異構的。 'Foo >'表示一個包含Foos的Foo,其中包含一些未指定的特定的,均勻的元素類型。 –

+0

這個想法是爲了推動'Foo >'工作在無限類型上,所以編譯器無法將它與一個有界的對應物相匹配。是的,我同意這個術語更多地假設'Foo'是一個它不需要的容器,但是stil的地位。 – Vikdor

+0

@JudgeMental:不是,'Foo >'是異構的,可以同時包含'Foo '和'Foo ''。 – newacct

0

一個好的閱讀What do multi-level wildcards mean?

例子:

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 

當然,我們可以指定一個Collection<Pair<String,Long>>Collection<Pair<String,Long>>。這裏沒有什麼奇怪的。

但我們不能將Collection<Pair<String,Long>>分配給Collection<Pair<String,?>>。參數化類型Collection<Pair<String,Long>>是一對String和一個Long的同類集合;參數化類型Collection<Pair<String,?>>是String和某些未知類型對的異構集合。異構的Collection<Pair<String,?>>例如可以包含Pair<String,Date>,並且明顯不屬於Collection<Pair<String,Long>>。出於這個原因,分配是不允許的。

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); 

現在,我們假設編譯器允許您使用f1作爲參數調用go()。 推斷T的選項是:

  1. T = Object,但ArrayList<ArrayList<Object>>不是ArrayList<ArrayList<?>>因爲ArrayList<Object>是不一樣的類型ArrayList<?> 所以這是不允許的選項。

  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) { 
} 
+0

你的證據似乎破裂了。爲什麼'alt.add(new Integer(1))'(我假定你的意思是'add')工作? 「T」和「Integer」之間的子類型是未知的,它將被拒絕。一個工作證明是:'f.get(1).add(f.get(0).get(0))',有效地將'String'添加到'Integer'列表中。 –

+0

@Ben Schulz謝謝你發現我的錯誤。固定。 –