簡體   English   中英

Java可以從類型參數邊界推斷出類型參數嗎?

[英]Can Java infer type arguments from type parameter bounds?

以下測試程序源自一個更復雜的程序,它可以執行一些有用的操作。 它與Eclipse編譯器成功編譯。

import java.util.ArrayList;
import java.util.List;

public class InferenceTest
{
    public static void main(String[] args)
    {
        final List<Class<? extends Foo<?, ?>>> classes =
            new ArrayList<Class<? extends Foo<?, ?>>>();
        classes.add(Bar.class);
        System.out.println(makeOne(classes));
    }

    private static Foo<?, ?> makeOne(Iterable<Class<? extends Foo<?, ?>>> classes)
    {
        for (final Class<? extends Foo<?, ?>> cls : classes)
        {
            final Foo<?, ?> foo = make(cls); // javac error here
            if (foo != null)
                return foo;
        }
        return null;
    }

    // helper used to capture wildcards as type variables
    private static <A, B, C extends Foo<A, B>> Foo<A, B> make(Class<C> cls)
    {
        // assume that a real program actually references A and B
        try
        {
            return cls.getConstructor().newInstance();
        }
        catch (final Exception e)
        {
            return null;
        }
    }

    public static interface Foo<A, B> {}

    public static class Bar implements Foo<Integer, Long> {}
}

但是,使用Oracle JDK 1.7 javac,它失敗了:

InferenceTest.java:18: error: invalid inferred types for A,B; inferred type does not
 conform to declared bound(s)
            final Foo<?, ?> foo = make(cls);
                                      ^
    inferred: CAP#1
    bound(s): Foo<CAP#2,CAP#3>
  where A,B,C are type-variables:
    A extends Object declared in method <A,B,C>make(Class<C>)
    B extends Object declared in method <A,B,C>make(Class<C>)
    C extends Foo<A,B> declared in method <A,B,C>make(Class<C>)
  where CAP#1,CAP#2,CAP#3 are fresh type-variables:
    CAP#1 extends Foo<?,?> from capture of ? extends Foo<?,?>
    CAP#2 extends Object from capture of ?
    CAP#3 extends Object from capture of ?
1 error

哪個編譯器是對的?

上面輸出的一個可疑方面是CAP#1 extends Foo<?,?> 我希望類型變量邊界是CAP#1 extends Foo<CAP#2,CAP#3> 如果是這種情況,那么CAP#1的推斷邊界將符合聲明的邊界。 但是,這可能是一個紅色的鯡魚,因為C確實應該被推斷為CAP#1 ,但錯誤消息是關於A和B.


請注意,如果我用以下代碼替換第26行,則兩個編譯器都接受該程序:

private static <C extends Foo<?, ?>> Foo<?, ?> make(Class<C> cls)

但是,現在我無法引用捕獲的Foo參數類型。

更新:兩個編譯器同樣接受(但也無用)是這樣的:

private static <A, B, C extends Foo<? extends A, ? extends B>>
    Foo<? extends A, ? extends B> make(Class<C> cls)

它本質上導致AB被簡單地推斷為Object ,因此在任何上下文中顯然都沒有用。 但是,它確實證實了我的理論,即javac只會對通配符邊界進行推斷,而不是捕獲邊界。 如果沒有人有更好的想法,這可能是(不幸的)答案。 (最終更新)


我意識到整個問題可能是TL; DR,但我會​​繼續以防其他人遇到這個問題......

基於JLS 7, §15.12.2.7基於實際參數推斷類型參數 ,我做了以下分析:

給定形式A << FA = FA >> F的約束:

最初,我們有一個形式為A << F約束,它表示類型A可以通過方法調用轉換(第5.3節 )轉換為類型F 這里, AClass<CAP#1 extends Foo<CAP#2, CAP#3>>FClass<C extends Foo<A, B>> 請注意,其他約束形式( A = FA >> F )僅在推理算法遞歸時出現。

接下來, C應通過以下規則推斷為CAP#1

(2.)否則,如果約束的形式為A << F

  • 如果F的格式為G<..., Yk-1, U, Yk+1, ...> ,其中U是涉及Tj的類型表達式,那么如果A具有格式G<..., Xk-1, V, Xk+1, ...>的超類型G<..., Xk-1, V, Xk+1, ...>其中V是類型表達式,該算法遞歸地應用於約束V = U

這里, GClassUTjCVCAP#1 遞歸應用到CAP#1 = C應該導致約束C = CAP#1

(3.)否則,如果約束的形式為A = F

  • 如果F = Tj ,則暗示約束Tj = A

到目前為止,分析似乎與javac輸出一致。 也許分歧的關鍵是是否繼續嘗試推斷AB 例如,給定此規則

  • 如果F的形式為G<..., Yk-1, ? extends U, Yk+1, ...> G<..., Yk-1, ? extends U, Yk+1, ...> ,其中U涉及Tj ,然后如果A的超類型是以下之一:
    • G<..., Xk-1, V, Xk+1, ...> ,其中V是類型表達式。
    • G<..., Xk-1, ? extends V, Xk+1, ...> G<..., Xk-1, ? extends V, Xk+1, ...>

然后,該算法遞歸地應用於約束V << U

如果CAP#1被認為是通配符(它捕獲的),那么這個規則適用,並且推理繼續遞歸,其中UFoo<A, B>VFoo<CAP#2, CAP#3> 如上所述,這將產生A = CAP#2B = CAP#3

但是,如果CAP#1只是一個類型變量,那么這些規則似乎都沒有考慮它的界限。 也許在規范部分末尾的這種讓步引用了這樣的情況:

類型推斷算法應被視為啟發式算法,旨在在實踐中表現良好。 如果它無法推斷出期望的結果,則可以使用顯式類型參數。

顯然,通配符不能用作顯式類型參數。 :-(

問題是您從以下推理約束開始:

class<#1>, #1 <: Foo<?, ?>

這為您提供了C的解決方案,即C =#1。

然后你需要檢查C是否符合聲明的邊界 - C的邊界是Foo,所以你最終得到這個檢查:

#1 <: Foo<A,B>

可以改寫為

Bound(#1) <: Foo<A, B>

因此:

Foo<?, ?> <: Foo<A, B>

現在,編譯器在這里進行LHS的捕獲轉換(這里是生成#2和#3的地方):

Foo<#2, #3> <: Foo<A, B>

意思是

A = #2

B = #3

所以,我們的解決方案是{A =#2,B =#3,C =#1}。

這是一個有效的解決方案? 為了回答這個問題,我們需要在類型替換后檢查推斷類型是否與推理變量邊界兼容,因此:

[A:=#2]A <: Object
#2 <: Object - ok

[B:=#3]B <: Object
#3 <: Object - ok

[C:=#1]C <: [A:=#2, B:=#3]Foo<A, B>
#1 <: Foo<#2, #3>
Foo<?, ?> <: Foo<#2, #3>
Foo<#4, #5> <: Foo<#2, #3> - not ok

因此錯誤。

當涉及推理和捕獲類型之間的相互作用時,規范是不明確的,因此在不同編譯器之間切換時有不同的行為是很正常的(但不是很好!)。 然而,從編譯器角度和JLS角度來看,其中一些問題正在得到解決,所以這樣的問題應該在中期得到解決。

我注意到的兩件事:

  1. CAP#1不是通配符,因為捕獲轉換 ,它是一個類型變量。

  2. 在第一步,JLS提到U是類型表達式,Tj是類型參數 JLS沒有明確定義類型表達式是什么,但我的直覺是它包含了類型參數的邊界。 如果是這種情況,則U將是C extends Foo<A,B>並且V將是CAP#1 extends Foo<CAP#2, CAP#3> 遵循類型推斷算法:

V = U - > C = CAP#1Foo<CAP#2, CAP#3> = Foo<A, B>

您可以繼續將類型推斷算法應用於上述,最終將得到A= CAP#2B=CAP#3

我相信你已經發現了Oracle編譯器的一個錯誤

暫無
暫無

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

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