为什么有必要在指定通用参数后指定它们?

时间:2015-05-29 05:46:46

标签: java generics

我有以下界面

public interface Foo<T extends Bar> {

    T getBar();
}

以下课程

public class FooFinder {

    public Foo<? extends Bar> getFoo(final String fooName) {
        return knownFoos.get(fooName);
    }

    private Map<String, Foo<? extends Bar>> knownFoos = new HashMap<>();
}

我的问题是这个。为什么我需要指定getFoo返回Foo<T extends Bar>knownFoos的值为Foo<T Extends Bar>,而Foo只能是Foo<T extends Bar>因为它的签名?我可能天真地认为它与类型擦除有关,但我不确定。

附录:

选择Foo界面。 T只能是BarBar的子类。代码和编译器清楚地说明了这一点。如果我们尝试以下

Foo<T extends Object> fooObj = new Foo<String>();

编译器会告诉您String不在绑定<T extends Bar>中。因此,在编译时,我们知道没有Foo可以存在,而不是T extends Bar。这与List<T>示例不同,因为除了T之外没有约束Object,除非您另行指定。

考虑到这一点,如果编译器足够聪明,知道Foo的范围只能是<T extends Bar>,为什么还要求我明确设置绑定?

2 个答案:

答案 0 :(得分:0)

只要您不介意使用其他语言,就不必这样做。

其他语言更多地使用称为类型推断的功能,它可以满足您的期望:查看代码并找出其中的内容。这当然需要一些代价。例如,Scala以长编译时间和有时令人困惑的情况而闻名,当类型推断没有推断开发人员应该使用的类型时。

使用Java几乎所有东西都必须手动指定(尽管现在使用&lt;&gt;运算符进行了一些类型推断。

您提到的替代方案

Foo

Foo<T extends Bar>

Foo<? extends Bar> 

are in fact different types,你必须明智地选择使用哪个。

答案 1 :(得分:0)

  

为什么我需要指定getFoo返回Foo&lt; ?扩展Bar&gt;

与什么相反?返回一个隐含使用泛型Bar的Foo?那是不一样的。

让我们使用一个具体的例子:

List<? extends Number> getList();

此方法签名会告诉您它返回List<Integer>List<Float>或任何其他Number子类型的List。但List只包含这一个子类型的对象。在这种情况下,通配符运算符用于向调用者表示您无法告诉他使用了哪个子类型。

这与返回数字列表不同,因为数字列表将被允许包含任何子类型为Number的对象。

这种区别非常重要:如果您返回List<Number>,则允许调用者将任何Number对象添加到List中。所以List会突然包含Numbers的混合,而之前它只包含整数。使用通配符运算符可以防止这种情况,因为编译器无法检查新Number是否具有正确的类型。

编译器检查它很重要。如果它没有,您可能会在代码的其他部分遇到ClassCastExceptions,其中List与具体泛型而不是通配符一起使用。