如何正确为具有上界的非静态方法进行参数化?

huangapple go评论71阅读模式
英文:

How to correctly parameterize non-static method with upper bound?

问题

public class Foo {

    public <T extends I> Collection<T> test(Class<T> clazz) {
        List<B> list = new ArrayList<>();
        list.add(new B());
        return list; // compilation error: required type Collection<T>
                     //                    provided List<B>
    }

    interface I {}

    interface A extends I {}

    class B implements A {}
}
英文:

I need to write parameterized method that would return collection of type T, where T can be object of any class that inherits I. It's trivial if T is defined on class level, but I need generic non-static method. Can't understand compiler, anything below seems logical to me.

public class Foo {

    public &lt;T extends I&gt; Collection&lt;T&gt; test(Class&lt;T&gt; clazz) {
        List&lt;B&gt; list = new ArrayList&lt;&gt;();
        list.add(new B());
        return list; // compilation error: required type Collection&lt;T&gt;
                     //                    provided List&lt;B&gt;
    }

    interface I {}

    interface A extends I {}

    class B implements A {}
}

答案1

得分: 2

这不是编译器让你感到困惑,而是概念本身让你感到困惑。

让我们非常实际地来看:我们有一个类类型层次结构:苹果(Apple)和梨(Pear)都扩展了水果(Fruit),而水果(Fruit)扩展了对象(Object)。

这里的关键是意识到:

List<Apple> 不能被视为 List<Fruit>

想象一下如果你能这样做:

List<Apple> apples = new ArrayList<Apple>();
List<Fruit> fruits = apples; // 这是不合法的
fruits.add(new Pear());
Apple apple = apples.get(0); // 但是 apples.get(0) 是... 一个梨???

这就是为什么你不能这样做。区别在于:你可以'写入'苹果和梨到集合中。

如果你想要了解更多,这被称为'变异性'。像 Fruit fruit = new Apple(); 这样的东西就是协变的(类型本身及其任何子类型都可以)。但是泛型是不变的,因为它必须是这样,参见上面的例子。甚至还有逆变性,其中类型或其任何__超类型__都可以。在泛型中,你必须明确选择变异性:

List<? extends Fruit> fruits = new ArrayList<Apple>(); // 协变
List<? super Apple> apples = new ArrayList<Fruit>(); // 逆变
List<Fruit> fruits = new ArrayList<Fruit>(); // 不变性(默认)

当你有一个'协变水果'的列表时,你__不能向其中添加任何东西。一点也不*。

当你有一个'逆变水果'的列表时,你可以很好地添加,但你不能从中获取任何东西。或者更确切地说,无论何时你从中获取结果,它们的类型只是 Object,这只是因为一切都保证是一个对象。

使用'不变水果'列表,你可以很好地添加__和__获取。

将你的代码更改为:public <T extends I> Collection<? extends T> test(...,它会起作用。但是 test(B.class).add(new B()); 不会起作用。你不能使两者都起作用,因为从根本上讲,这是不可能的。

*)除了字面上的 null,因为它适用于任何类型。对于变异性的一般原则来说,这是无关紧要的。

英文:

It's not the compiler that's confusing you, it's the concepts themselves.

Let's make this very pragmatic: We have a class type hierarchy: Apple and Pear both extend Fruit, and Fruit extends Object.

Here's the key realization:

A List&lt;Apple&gt; cannot just be treated as a List&lt;Fruit&gt;.

Imagine you could do that:

List&lt;Apple&gt; apples = new ArrayList&lt;Apple&gt;();
List&lt;Fruit&gt; fruits = apples; // This is not legal
fruits.add(new Pear());
Apple apple = apples.get(0); // but apples.get(0) is.. a pear???

That's why you can't do that. The difference is: You can 'write' apples and pears INTO a collection.

If you want to learn all about it, it's called 'variance'. Something like Fruit fruit = new Apple(); is covariance in action (the type itself and any subtype thereof is fine). But generics is invariant, because it has to be, see above example. There's even contravariance, where a type or any supertype thereof is fine. In generics you have to explicitly choose variance:

List&lt;? extends Fruit&gt; fruits = new ArrayList&lt;Apple&gt;(); // covariance
List&lt;? super Apple&gt; apples = new ArrayList&lt;Fruit&gt;(); // contravariance
List&lt;Fruit&gt; fruits = new ArrayList&lt;Fruit&gt;(); // invariance (Default)

When you have a 'covariant fruits' list, you cannot add anything to it. At all*.

When you have a 'contravariant fruits' list, you can add just fine, but you cannot get anything from it. Or rather, whenever you get results out, their type is just Object, that works only because everything is guaranteed to be an Object.

With an 'invariant fruits' list, you can add and get just fine.

Change your code to read: public &lt;T extends I&gt; Collection&lt;? extends T&gt; test(... and it'll work. But test(B.class).add(new B()); wouldn't. You can't have both work, because fundamentally that cannot work.

*) Except literally null because its every type. Irrelevant for the general principle of variance.

huangapple
  • 本文由 发表于 2020年10月21日 19:54:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/64462949.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定