可以从类型参数边界Java推断类型参数吗?

以下测试程序是从一个比较复杂的程序中得出的,这个程序有用。它与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扩展了Foo<?,?>。我期望类型变量边界是CAP#1扩展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)

它基本上导致A和B被简单地推断为Object,因此在任何上下文中显然不是有用的。但是,在javac只会对通配符边界执行推理,而不是捕获边界,这可以帮助我理解。如果没有人有更好的想法,这可能是(不幸的)答案。 (结束更新)

我意识到这个整个问题很可能是TL; DR,但是我会继续,以防别人遇到这个问题?

基于JLS 7,§15.12.2.7 Inferring Type Arguments Based on Actual Arguments,我做了以下分析:

Given a constraint of the form A << F, A = F, or A >> F:

最初,我们有一个形式为A< F,表示A类可通过方法调用转换(§5.3)转换为F型。这里,A是Class< CAP#1扩展Foo< CAP#2,CAP#3>>而F是Class< C扩展Foo< A,B>。注意,其他约束形式(A = F和A>> F)仅在推论算法递归时出现。

接下来,应通过以下规则推断C为CAP#1:

(2.) Otherwise, if the constraint has the form A << F:

  • If F has the form G<..., Yk-1, U, Yk+1, ...>,
    where U is a type expression that involves Tj,
    then if A has a supertype of the form G<..., Xk-1, V, Xk+1, ...>
    where V is a type expression,
    this algorithm is applied recursively to the constraint V = U.

这里,G是Class,U和Tj是C,V是CAP#1。 CAP#1 = C的递归申请应导致约束C = CAP#1:

(3.) Otherwise, if the constraint has the form A = F:

  • If F = Tj, then the constraint Tj = A is implied.

到目前为止,分析似乎与javac输出一致。也许分歧点是是否继续尝试推断A和B.例如,给定这个规则

  • If F has the form G<..., Yk-1, ? extends U, Yk+1, ...>,
    where U involves Tj, then if A has a supertype that is one of:

    • G<..., Xk-1, V, Xk+1, ...>, where V is a type expression.
    • G<..., Xk-1, ? extends V, Xk+1, ...>.

Then this algorithm is applied recursively to the constraint V << U.

如果CAP#1被认为是通配符(它是捕获的),则该规则适用,并且推理以U作为Foo递归递归A,B> V为Foo< CAP#2,CAP#3>。如上所述,这将产生A = CAP#2和B = CAP#3。

然而,如果CAP#1只是一个类型变量,那么这些规则似乎都不会考虑它的界限。也许在规范的这一节末尾的让步是指这种情况:

The type inference algorithm should be viewed as a heuristic, designed to perform well in practice. If it fails to infer the desired result, explicit type parameters may be used instead.

显然,通配符不能用作显式类型参数。 🙁

问题是你从以下推理约束开始:

<#1>,#1&lt ;:Foo<?,?>

其中给出了C的解决方案,即C =#1。

那么你需要检查C是否符合声明的界限 – C的范围是Foo,所以你最终得到这个检查:

#1<:Foo< A,B>

可以重写为

绑定(#1)&lt ;:Foo< A,B>

因此:

Foo<?,?> &lt ;:Foo< A,B>

现在,这里编译器对LHS进行捕获转换(这里生成#2和#3):

Foo#2,#3> &lt ;:Foo< A,B>

意思是

A =#2

B =#3

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

这是一个有效的解决方案吗?为了回答这个问题,我们需要检查推断的类型是否与推理变量边界兼容,在类型替换之后,所以:

[A:=#2] A<:Object
#2<:Object - ok [B:=#3] B<:对象
#3<:Object - 好的 [C:=#1] C <= [A:=#2,B:=#3] Foo< A,B&
#1&lt ;:Foo<#2,#3>
Foo<?,?> &lt ;:Foo<#2,#3>
Foo#4,#5> &lt ;:Foo<#2,#3> – 不好

所以出错。

当涉及到推断和捕获类型之间的相互作用时,这个规范是不明确的,所以在不同的编译器之间进行切换时,它是非常正常的(但不是很好!)有不同的行为。然而,从编译器的角度和从JLS的角度来看,这些问题中的一些问题正在进行中,所以这样的问题应该在中期得到修正。

http://stackoverflow.com/questions/17438206/can-java-infer-type-arguments-from-type-parameter-bounds

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:可以从类型参数边界Java推断类型参数吗?