置顶:小白做笔记,逻辑混乱,轻喷
首先先回顾数组参数是如何多态化运行的
普通数组工作的方式
/**
* @ Author :cy
* @ Date :16:08 2020/7/9
*/
abstract class Animal{
void eat(){
System.out.println("animal eating");
}
}
class Dog extends Animal{
void bark(){ }
}
class Cat extends Animal{
void meow(){ }
}
public class TestGenerics1 {
public static void main(String[] args) {
new TestGenerics1().go();
}
public void go(){
Animal[] animals = {new Dog(), new Cat(), new Dog()};
Dog[] dogs = {new Dog(), new Dog(), new Dog()};
takeAnimals(animals);
takeAnimals(dogs);
}
public void takeAnimals(Animal[] animals){
for(Animal a : animals){
a.eat();
}
}
}
运行结果:
(忘记加分隔符。将就看吧)
补充:我们只能调用声明在 Animal 中的方法,因为方法的参数是 Animal 数组,且无需任何类型转换(Animal 数组中有两个子类(Dog,Cat),如果要转换需先判断类型)
以上是数组参数多态化运行
ArrayList 参数多态化运行
对上面的例子稍加修改 ( 展示修改部分 )
public void go(){
/*Animal[] animals = {new Dog(), new Cat(), new Dog()};
Dog[] dogs = {new Dog(), new Dog(), new Dog()};
takeAnimals(animals);
takeAnimals(dogs);*/
ArrayList<Animal> animals = new ArrayList<>();
animals.add(new Dog());
animals.add(new Cat());
animals.add(new Dog());
takeAnimals(animals);
}
public void takeAnimals(ArrayList<Animal> animals){
for(Animal a : animals){
a.eat();
}
}
运行结果:
问题来了,如果我们使用 ArrayList ,编译器会用过编译吗?通过了可以执行吗?
public void go(){
/*Animal[] animals = {new Dog(), new Cat(), new Dog()};
Dog[] dogs = {new Dog(), new Dog(), new Dog()};
takeAnimals(animals);
takeAnimals(dogs);*/
/*ArrayList<Animal> animals = new ArrayList<>();
animals.add(new Dog());
animals.add(new Cat());
animals.add(new Dog());
takeAnimals(animals);*/
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
takeAnimals(dogs);
}
显然,当我们写 takeAnimals(dogs); 时,编译器会告诉我们参数类型不兼容。
回看我们第一个例子(数组参数多态化运行),在例子中,我们传递了数组dogs,程序并没有报错,还正常执行了。这是为啥???难道在这里使用多态出问题了?按照我对多态的理解,Animal 可以做的事情(例如 eat) ,Dog 也能做,所以对Dog引用调用 eat() 方法应该是没错的。
假设编译器不报错,那么问题来了,如果程序出现了下面这段代码呢?
public void takeAnimals(ArrayList<Animal> animals){
animals.add(new Cat());
/* for(Animal a : animals){
a.eat();
}*/
}
这里就出现问题了,我们在 go() 方法中,传入的是 ArrayList< Dog > 的 dogs, 这里加入 Cat 类型不太合适吧,但是在 takeAnimals() 方法中,参数是这样定义的 :ArrayList< Animal > animals,它加入一个Cat对象到集合中不会有错误吧。
到这里,我们大概就能明白为什么 takeAnimals(dogs); 会报错了
也就是说,如果把方法声明成取用 ArrayList< Animal > ,它就只会取用ArrayList< Animal >参数,ArrayList< Dog >与ArrayList< Cat >都不行
此时,你可能会想,如果我在Dog数组中这么做会出现什么情况呢
public void go(){
Animal[] animals = {new Dog(), new Cat(), new Dog()};
Dog[] dogs = {new Dog(), new Dog(), new Dog()};
takeAnimals(animals);
takeAnimals(dogs);
}
public void takeAnimals(Animal[] animals){
animals[0] = new Cat();
for(Animal a : animals){
a.eat();
}
}
哦吼,我的 idea 此时没有提示错误,是不是他就可以运行呢?
运行结果:
原因:
数组的类型是在运行期间检查的,但集合的类型检查是在编译期间。
怎样才能实现使用多态化集合参数?(传入Dog类型的集合,并可以调用Animal 的 eat方法,禁止将 与传入的集合 不匹配 的对象 加入 到集合中 )
终于来到重点了——万用字符(是不是觉得前面讲了一堆废话)
先看代码
public void takeAnimals(ArrayList<? extends Animal> animals){
for(Animal a : animals){
a.eat();
}
}
这时,有人可能会说,你这样不也是还可以加入其它类型的对象?
实际上,在使用带有<?>的声明时,编译器不会让你加入任何东西到集合中
此处插播一个小知识点
直接看结论
↓ ↓ ↓
这就是万用字符!
注:
public <T extends Animal> void todo( ArrayList<T> list ){}
public void todo1( ArrayList<? extends Animal> list ){}
这两种写法实现的效果是一样的