英文:
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 <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 {}
}
答案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<Apple>
cannot just be treated as a List<Fruit>
.
Imagine you could do that:
List<Apple> apples = new ArrayList<Apple>();
List<Fruit> 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<? extends Fruit> fruits = new ArrayList<Apple>(); // covariance
List<? super Apple> apples = new ArrayList<Fruit>(); // contravariance
List<Fruit> fruits = new ArrayList<Fruit>(); // 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 <T extends I> Collection<? extends T> 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论