首页 > 解决方案 > 为什么 javac 不能对带有有界类型参数作为返回类型的静态方法的调用站点进行类型检查?

问题描述

为什么javac在此代码示例中没有因类型错误而中止

import java.util.List;

public class StaticMethodWithBoundedReturnType {
    static class Foo {
    }

    static class Bar extends Foo {
    }

    static <F extends Foo> F getFoo(String string) {
        …
    }

    public static void main(String[] args) {
        // Compiles without error, even though List does not extend Foo.
        List<Integer> list = getFoo("baz");
    }
}

显然List永远不可能是 的子类型Foo。即使存在一个子类型List会以某种方式扩展Foo,那么list在调用站点的分配getFoo()应该是无效的。我知道类型擦除的存在。但是不javac应该能够看到的类型list不满足有界类型约束extends Foo,从而导致编译失败并出现类型错误?

为什么javac不能使用有界类型参数作为返回类型对静态方法的调用站点进行类型检查?

看来我可以通过以下轻微修改获得类型安全:

import java.util.List;

public class StaticMethodWithBoundedReturnType {
    static class Foo {
    }

    static class Bar extends Foo {
    }

    static <F extends Foo> F getFoo(String string, Class<F> clazz) {
        …
    }

    public static void main(String[] args) {
        // Does not compile \o/
        List<Integer> list = getFoo("baz", List.class);
    }
}

但这需要将Class参数添加到 getFoo() 中,该参数根本没有在方法的主体中使用。有没有更好的方法来实现类型安全?

标签: javagenericsreturn-type

解决方案


要理解这一点,我们需要了解以下内容的实际含义:

static <F extends Foo> F getFoo(String string) {
    return null;
}

这表示getFoo返回某种类型的值,该值必须从进行调用的上下文中推断出来。此外,它做出了一个约束,即推断的类型必须是 的子类型Foo

由于null可分配给所有可能的引用类型,因此它适合作为返回值。事实上,它是唯一可能被退回的。

为了说明,请尝试以下变体:

import java.util.List;

public class StaticMethodWithBoundedReturnType {
    static class Foo {
    }

    static class Bar extends Foo {
    }

    static <F extends Foo> F getFoo(String string) {
        return new Bar();
    }

    public static void main(String[] args) {
        // Compiles without error, even though List does not extend Foo.
        List<Integer> list = getFoo("baz");
    }
}

这会产生编译错误

StaticMethodWithBoundedReturnType.java:11: error: incompatible types:
Bar cannot be converted to F
        return new Bar();
               ^

你可能会问:为什么 Bar 和 F 不兼容?.

答:因为F代表( R & ? extends Foo )whereR是结果getFoo分配给的类型。而且,在getFoore 中无法知道R会发生什么。(事实上​​,它可以有很多不同的类型!)

简而言之,类型签名

    <F extends Foo> F getFoo(String string)

是有问题的。但是,考虑一下:

static <F extends Foo> F getFoo(Class<F> clazz) {
    return class.newInstance();
}

这是合法的,并且将返回一个满足运行时类型安全性(本地)的值。但是,如果您尝试将其分配给 a ,您将得到预期的编译错误List<Integer>

StaticMethodWithBoundedReturnType.java:16: error: incompatible types:
inference variable F has incompatible bounds
        List<Integer> list = getFoo(Bar.class);
                                   ^
    equality constraints: Bar
    upper bounds: List<Integer>,Foo
  where F is a type-variable:
    F extends Foo declared in method <F>getFoo(Class<F>)
1 error

回到示例,考虑调用:

 List<Integer> list = getFoo("baz");

这是合法的,因为结果的推断类型是交集类型(List<Integer> & ? extends Foo)。确实,这种交集类型是可以实现的;例如作为

 class Baz extends Bar implements List<Integer> { /* list methods */ }

(在我们的程序中这个类中没有实现这一事实Baz无关紧要。可能有一个。)

这样,我们就可以编译程序了。并且当我们执行它时,list将被赋值null,这不是运行时类型违规。


推荐阅读