2016-07-07 33 views
16

這段代碼編譯在Eclipse中,但不是在javac的:下界通配符引起麻煩的javac,而不是Eclipse的

import java.util.function.Consumer; 

public class Test { 
    public static final void m1(Consumer<?> c) { 
     m2(c); 
    } 
    private static final <T> void m2(Consumer<? super T> c) { 
    } 
} 

javac的輸出:

C:\Users\lukas\workspace>javac -version 
javac 1.8.0_92 

C:\Users\lukas\workspace>javac Test.java 
Test.java:5: error: method m2 in class Test cannot be applied to given types; 
     m2(c); 
     ^
    required: Consumer<? super T> 
    found: Consumer<CAP#1> 
    reason: cannot infer type-variable(s) T 
    (argument mismatch; Consumer<CAP#1> cannot be converted to Consumer<? super T>) 
    where T is a type-variable: 
    T extends Object declared in method <T>m2(Consumer<? super T>) 
    where CAP#1 is a fresh type-variable: 
    CAP#1 extends Object from capture of ? 
1 error 
---------------------------------------------------------------- 

哪個編譯器是錯誤的,爲什麼? (Eclipse bug report and parts of discussion here

回答

9

此代碼對於JLS 8是合法的。javac版本8及更早版本在處理通配符和捕獲方面存在一些錯誤。從版本9開始(早期訪問,我嘗試版本ea-113和更新版本)也javac接受此代碼。

爲了理解編譯器如何根據JLS分析這個問題,必須區分通配符捕獲,類型變量,推理變量等。

c的類型是Consumer<capture#1-of ?>(javac會寫Consumer<CAP#1>)。這種類型是未知的,但是是固定的。

m2的參數的類型爲Consumer<? super T>,其中T是一個要通過類型推斷實例化的類型變量。

在類型推斷期間,用推斷變量,用ecj表示爲T#0,用於表示T

類型推斷包括確定T#0是否可以實例化爲任何類型而不違反任何給定的類型約束。在這個特殊情況下,我們從這個約束開始:

⟨c→消費<?超級T#0>⟩

其是逐步降低(通過施加JLS 18.2):

⟨Consumer<捕獲#1-的>→消費者<? super T#0>⟩

⟨capture#1-of? < =?超級T#0⟩

⟨T#0 <:捕獲#1的⟩

T#0 <:?捕獲#1-的?

最後一行是「類型綁定」,並且完成了縮減。由於不涉及進一步的限制,因此解決方法將T#0例示爲capture#1-of ?

通過這些步驟,類型推斷已經證明m2適用於此特定的調用。 QED。

直觀上,所示的解決方案告訴我們:無論捕獲可能表示什麼類型,如果T設置爲表示完全相同的類型,則不會違反類型約束。這是可能的,因爲捕獲在之前固定爲開始類型推斷。

+1

即使'javac'版本早9b29編譯這個唯一。 – Holger

+1

非常感謝您深入挖掘這一點。很高興看到使用Java 9,現在兩種編譯器的行爲方式都是一樣的! :) –

+2

我也很欣慰,看到這個融合:) –

5

注意,下面可以毫無問題地編譯:

public class Test { 
    public static final void m1(Consumer<?> c) { 
     m2(c); 
    } 
    private static final <T> void m2(Consumer<T> c) { 
    } 
} 

儘管我們不知道實際類型的消費者,我們知道這將是分配給Consumer<T>,雖然我們不知道什麼T是(不知道什麼T是,無論如何是泛型代碼的常態)。

但是,如果對Consumer<T>的分配有效,那麼也可以分配到Consumer<? super T>。我們甚至可以用一箇中間步驟來實際顯示它:

public class Test { 
    public static final void m1(Consumer<?> c) { 
     m2(c); 
    } 
    private static final <T> void m2(Consumer<T> c) { 
     m3(c); 
    } 
    private static final <T> void m3(Consumer<? super T> c) { 
    } 
} 

沒有編譯器對象。

當您用指定類型替換通配符時,它也會被接受,例如,

public class Test { 
    public static final void m1(Consumer<?> c) { 
     m2(c); 
    } 
    private static final <E,T extends E> void m2(Consumer<E> c) { 
    } 
} 

這裏,E是一個超級類型的T,就像? super T是。

我試圖找到最接近這個場景的javac的錯誤報告,但是當涉及到javac和通配符類型時,它們有很多,我最終放棄了。免責聲明:這並不意味着有這麼多的錯誤,只是有很多相關的情況報告,這可能都是同一個錯誤的不同症狀。

的事項是,它已經固定在Java中9