英文:
Why can't List<? extends Animal> be replaced with List<Animal>?
问题
考虑以下代码:
public class Main {
static class Animal {}
static class Dog extends Animal {}
static List<? extends Animal> foo() {
List<Dog> dogs = new ArrayList<>();
return dogs;
}
public static void main(String[] args) {
List<Animal> dogs = Main.foo(); // 编译错误
}
}
我试图理解为什么这段代码无法编译通过。也就是说,为什么编译器不允许我将 List<? extends Animal>
引用为 List<Animal>
?
这是否与类型擦除机制有关?
英文:
Consider the following code:
public class Main {
static class Animal {}
static class Dog extends Animal {}
static List<? extends Animal> foo() {
List<Dog> dogs = new ArrayList<>();
return dogs;
}
public static void main(String[] args) {
List<Animal> dogs = Main.foo(); // compile error
}
}
I'm trying to understand why it won't compile. Meaning, why doesn't the compiler let me refer to List<? extends Animal>
as a List<Animal>
?
Is that has something to do with the type erasure mechanism?
答案1
得分: 11
A List<Animal>
是一个 List
,你可以向其中添加 任何 Animal
(或null),而你取出的一切都将是一个 Animal
。
A List<? extends Animal>
是一个列表,其中只包含 Animal 的特定子类(或null),你不知道是哪一个;这允许你将从中取出的一切都视为 Animal
,但你不允许向其中添加任何东西(除了字面上的 null
)。
List<? extends Animal>
不能充当 List<Animal>
,因为这会允许你这样做:
List<Cat> listOfCats = new ArrayList<>();
List<? extends Animal> listOfSomeAnimals = listOfCats; // Fine.
List<Animal> listOfAnimals = listOfSomeAnimals; // Error, pretend it works.
listOfAnimals.add(new Dog());
现在,因为 listOfCats
、listOfSomeAnimals
和 listOfAnimals
都是同一个列表,所以 Dog
已被添加到 listOfCats
。因此:
Cat cat = listOfCats.get(0); // ClassCastException.
英文:
A List<Animal>
is a List
to which you can add any Animal
(or null), and everything you take out of it will be an Animal
.
A List<? extends Animal>
is a list which contains only a specific subclass of Animal
(or null), and you don't know which one; this allows you to treat everything you take out of it as an Animal
, but you aren't allowed to add anything to it (except for literal null
).
A List<? extends Animal>
can't act as a List<Animal>
, because that would allow you to do this:
List<Cat> listOfCats = new ArrayList<>();
List<? extends Animal> listOfSomeAnimals = listOfCats; // Fine.
List<Animal> listOfAnimals = listOfSomeAnimals; // Error, pretend it works.
listOfAnimals.add(new Dog());
Now, because listOfCats
, listOfSomeAnimals
and listOfAnimals
are all the same list, the Dog
has been added to listOfCats
. As such:
Cat cat = listOfCats.get(0); // ClassCastException.
答案2
得分: 4
因为List<? extends Animal>
会允许任何Animal的子类。List<Animal>
只允许Animal类的对象。
在List<? extends Animal>
中,也允许像猫或狗这样的对象。如果你用一个“纯粹”的狗列表来初始化它,从外部看不出不允许,因此它不会编译。
英文:
Because List<? extends Animal>
would allow any subclass of Animal. List<Animal> would simply allow objects of the Animal class.
In List<? extends Animal>
are also objects like cat or dog allowed. If you initiate this with a "pure" dog-list, you can't tell from the outside that it's not allowed and therefore it doesn't compile.
答案3
得分: 3
# 在Java中的共变、逆变和不变性
这是关于共变、逆变和不变性的内容。协变告诉我们可以取出什么,逆变告诉我们可以放入什么,而不变性告诉我们关于两者都是什么。
**不变性**
`List<Animal>` 是**不变的**。你可以添加任何动物,你保证可以取出*任何*动物 - `get(int)` 给我们一个 `Animal`,`add(Animal)` 必须接受*任何*动物。我们可以放入一个动物,我们得到一个动物。
`List<Animal> animals = new ArrayList<Dog>()` 是一个*编译错误*,因为它不接受 `Animal` 或 `Cat`。`get(int)` 仍然只给我们动物(毕竟,狗是动物),但不接受其他动物是一个破绽。
`List<Animal> animals = new ArrayList<Object>()` 同样是一个破绽。是的,它接受任何动物(我们可以放入动物),但是它给我们对象。
**逆变性**
`List<? super Dog>` 是**逆变的**。我们只能放入狗,但是对于我们取出什么没有明确说明。因此,我们得到对象。
`List<? super Dog> dogs = new ArrayList<Animal>()` 这可以工作,因为我们*可以*把狗放进去。而且动物是对象,所以我们可以取出对象。
````java
List<? super Dog> dogs = new ArrayList<Animal>();
// dogs.add(new Animal()); // 编译错误,必须放入狗
dogs.add(new Dog());
Object obj = dogs.get(0);
// Dog dog = dogs.get(0); // 编译错误,只能取出对象
协变性
List<? extends Animal>
是协变的。你保证可以取出动物。
List<? extends Animal> animals = new ArrayList<Cat>()
可以工作,因为猫是动物,而 get(n)
给你的是动物。确实,它们都是猫,但猫是动物,所以这完全可以工作。
不过,添加东西会更加困难,因为你实际上没有一个可以放入的类型:
List<? extends Animal> animals = new ArrayList<Cat>();
//animals.add(new Cat()); // 编译错误
//animals.add(new Animal()); // 编译错误
Animal animal = animals.get(0);
List<? extends Cat> cats = new ArrayList<Animal>()
是一个编译错误,因为你可以取出任何动物 - 但你要求唯一可以取出的东西是猫。
你的代码
static List<? extends Animal> foo() {
List<Dog> dogs = new ArrayList<>();
return dogs;
}
在这里,一切都很好。foo()
是一个列表,你可以从中取出动物。你肯定,因为狗是动物,你可以取出狗,你可以取出动物。你从列表中取出的每一样东西都保证是一个动物。
List<Animal> dogs = Main.foo(); // 编译错误
你说 dogs
是一个列表,你可以放入任何动物,你保证可以取出动物。后面的部分很简单,没错,你保证可以取出动物,这就是 ? extends Animal
的意思。但是你不能放入任意的动物。这就是为什么这失败的原因。
<details>
<summary>英文:</summary>
# Co-, Contra- & Invariance in Java
This is about Co-, Contra- and Invariance. Covariance tells us about what we can take out, Contravariance tells us about what we can put in, and Invariance tells us about both.
**Invariance**
`List<Animal>` is **invariant**. You can add any Animal, and you are guaranteed to get *any* Animal out - `get(int)` gives us an `Animal`, and `add(Animal)` must accept *any* Animal. We can put an Animal in, we get an Animal out.
`List<Animal> animals = new ArrayList<Dog>()` is a *compiler error*, since it doesn't accept `Animal` or `Cat`. `get(int)` still gives us only Animals (Dogs are Animals, after all), but not accepting the others is a deal-breaker.
`List<Animal> animals = new ArrayList<Object>()` is likewise a deal-breaker. Yes, it accepts any animal (we can put Animals in), but it gives us Objects.
**Contravariance**
`List<? super Dog>` is **contravariant**. We can only put Dogs in, put nothing is said about what we get out. Thus, we get Object out.
`List<? super Dog> dogs = new ArrayList<Animal>();` this works, because we *can* put a Dog into it. And Animals are Objects, so we can get objects out.
````java
List<? super Dog> dogs = new ArrayList<Animal>();
// dogs.add(new Animal()); // compile error, need to put Dog in
dogs.add(new Dog());
Object obj = dogs.get(0);
// Dog dog = dogs.get(0); // compile error, can only take Object out
Covariance
List<? extends Animal>
is covariant. You are guaranteed to get an Animal out.
List<? extends Animal> animals = new ArrayList<Cat>();
works, because cats are Animals, and get(n)
gives you Animals. Granted, they are all Cats, but Cats are Animals, so this works out fine.
Adding stuff is harder, though, since you don't actually have a type that you can put in:
List<? extends Animal> animals = new ArrayList<Cat>();
//animals.add(new Cat()); // compile error
//animals.add(new Animal()); // compile error
Animal animal = animals.get(0);
List<? extends Cat> cats = new ArrayList<Animal>();
is a compiler error, because you can take out any animal - but you require that the only thing that can be taken out is Cats.
Your code
static List<? extends Animal> foo() {
List<Dog> dogs = new ArrayList<>();
return dogs;
}
Here, everything is fine. foo()
is a List where you can take out Animals. You surely Since Dogs are Animals, and you can take out Dogs, you you can take out Animals. Everything you take out of the List is guaranteed to be an Animal.
List<Animal> dogs = Main.foo(); // compile error
You are saying that dogs
is a List where you can put in any Animal
, and you are guaranteed to get Animals out. The last part is easy, yes, you are guaranteed to get Animals out, that is what ? extends Animal
means. But you can't put arbitrary Animals in. And that is why this fails.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论