首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java可以从类型参数边界推断类型参数吗?

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

Stack Overflow用户
提问于 2013-07-03 09:07:40
回答 2查看 4.4K关注 0票数 20

下面的测试程序是从一个更复杂的程序派生而来的,该程序做了一些有用的事情。它使用Eclipse编译器成功编译。

代码语言:javascript
复制
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,它失败了,错误如下:

代码语言:javascript
复制
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行替换为以下代码,则两个编译器都接受该程序:

代码语言:javascript
复制
private static <C extends Foo<?, ?>> Foo<?, ?> make(Class<C> cls)

但是,现在我不能引用捕获的Foo参数类型。

更新:同样被两个编译器接受(但也无用)是这样的:

代码语言:javascript
复制
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,但我会继续下去,以防其他人触及这个问题……

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

给定形式为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)仅在推理算法递归时出现。

接下来,应根据以下规则推断CCAP#1

(2.)否则,如果约束的形式为A << F

  • 如果F具有G<..., Yk-1, U, Yk+1, ...>形式,其中U是涉及Tj的类型表达式,则如果A具有G<..., Xk-1, V, Xk+1, ...>形式的超类型,其中V是类型表达式,则此算法将递归应用于约束the

这里,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, ...>,其中U涉及Tj,则如果A具有下列类型之一的超类型:V where是Tj类型

然后将该算法递归地应用于约束V << U

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

然而,如果CAP#1仅仅是一个类型变量,那么似乎没有一条规则考虑它的界限。也许规范中这一节末尾的让步是指这样的情况:

类型推断算法应该被视为启发式算法,设计为在实践中表现良好。如果它无法推断出所需的结果,则可以改为使用显式类型参数。

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

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-07-04 02:22:49

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

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的角度来看,这些问题中的一些正在进行中,所以像这样的问题应该在中期内得到解决。

票数 10
EN

Stack Overflow用户

发布于 2013-07-03 10:48:04

我注意到两件事:

由于capture conversion

  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编译器的一个错误

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17438206

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档