簡體   English   中英

JLS如何指定不能在方法中正式使用通配符?

[英]How does the JLS specify that wildcards cannot be formally used within methods?

我一直想知道Java泛型的一些奇怪方面和通配符的使用。 比方說,我有以下API:

public interface X<E> {
    E get();
    E set(E e);
}

然后,假設我們聲明了以下方法:

public class Foo {
    public void foo(X<?> x) {
        // This does not compile:
        x.set(x.get());
    }

    public <T> void bar(X<T> x) {
        // This compiles:
        x.set(x.get());
    }
}

從我的“直觀”理解來看, X<?>實際上與X<T>相同,只是未知的<T>在客戶端代碼中是正式未知的。 但是在foo()里面,我猜想編譯器可以推斷出(偽JLS代碼) <T0> := <?>[0], <T1> := <?>[1], etc... 這是大多數程序員明確而直觀地做的事情。 他們委托私人助手方法,導致許多無用的鍋爐板代碼:

public class Foo {
    public void foo(X<?> x) {
        foo0(x);
    }

    private <T> void foo0(X<T> x) {
        x.set(x.get());
    }
}

另一個例子:

public class Foo {
    public void foo() {
        // Assuming I could instanciate X
        X<?> x = new X<Object>();
        // Here, I cannot simply add a generic type to foo():
        // I have no choice but to introduce a useless foo0() helper method
        x.set(x.get());
    }
}

換句話說,編譯器知道x.set()中的通配符與x.get()的通配符正式相同。 為什么不能使用這些信息? JLS中是否有正式的方面,這解釋了這個缺乏編譯器的“功能”?

你想知道它為什么不支持? 一個同樣有效的問題是, 為什么要支持它?

如果聲明X<? extends Y> x通配符的工作方式是X<? extends Y> x X<? extends Y> x ,表達式x.get()類型不是? extends Y ? extends Y或編譯器推斷的某種類型,但是Y 現在, Y不可分配給? extends Y ? extends Y ,所以你不能將x.get()傳遞給x.set ,它具有void set(? extends Y e)的有效簽名void set(? extends Y e) 你實際上唯一可以傳遞給它的是null ,它可以分配給每個引用類型。

我非常密切地關注JDK開發人員的郵件列表,而且一般的精神是JLS指定的每個功能都應該自己負擔 - 成本/收益比應該在利益方面很重。 現在,跟蹤一個值的來源是很有可能的,但它只能解決一個特殊情況,它有幾種其他方法可以解決它。 考慮這些類:

class ClientOfX {
    X<?> member;
    void a(X<?> param) {
        param.set(param.get());
    }
    void b() {
        X<?> local = new XImpl<Object>();
        local.set(local.get());
    }
    void c() {
        member.set(member.get());
    }
    void d() {
        ClassWeCantChange.x.set(ClassWeCantChange.x.get());
    }
}

class ClassWeCantChange {
    public static X<?> x;
}

這顯然不能編譯,但是根據您提出的增強功能,它確實可以。 但是,我們可以在不更改JLS的情況下編譯四種方法中的三種:

  1. 對於a ,當我們接收通用對象作為參數時,我們可以使該方法通用並接收X<T>而不是X<?>
  2. 對於bc ,當使用本地聲明的引用時,我們不需要使用通配符聲明引用(Microsoft的C#編譯器團隊的Eric Lippert稱這種情況“如果你這樣做會傷害,那么就不要這樣做! )。
  3. 對於d ,當使用我們無法控制其聲明的引用時,除了編寫輔助方法之外別無選擇。

因此,更智能的類型系統實際上只有在我們無法控制變量聲明的情況下才有用。 在這種情況下,做你想要做的事情的情況有點粗略 - 通配類型的通配行為通常意味着該對象要么僅作為生產者( ? extends Something )或消費者( ? super Something的對象,而不是兩者。

TL; DR版本是它帶來的好處(並且要求通配符不是它們現在的東西!),同時增加了JLS的復雜性。 如果有人想出一個非常引人注目的案例來添加它,那么規范可以延長 - 但是一旦它存在,就會永遠存在,並且它可能會導致無法預料的問題,所以在你真正需要它之前就把它留下來。

編輯:注釋包含有關通配符是什么和不通配符的更多討論。

我剛剛看到了Collections.swap()這種特殊實現:

public static void swap(List<?> list, int i, int j) {
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

JDK家伙采用原始類型,以便在本地處理此問題。 對我來說,這是一個強有力的聲明,表明這可能應該由編譯器支持,但由於某種原因(例如,沒有時間正式指定這一點)只是沒有完成

沒有參與編寫JLS,我最好的猜測就是簡單。 想象一下愚蠢但(語法上)有效的表達式,例如(x = new X<Integer>()).set(x.get()); 跟蹤捕獲的通配符可能會變得棘手。

我認為捕捉通配符不是作為樣板,而是做了正確的事情。 這表明您的API沒有客戶端代碼不需要的類型參數。

編輯:當前行為在JLS§4.5.2中指定。 x為每個成員訪問進行捕獲轉換 - 一次用於x.set(...) ,一次用於x.get() 捕獲是不同的,因此不知道它們代表相同的類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM