java – 理解泛型
java泛型相比C++模板要简单的多得多,只不过java泛型标准引入的时候因为历史版本兼容性的原因受到了一些限制,我们大可不必拘泥于刻板的语法强调说明,让我们一起来把握一下java泛型最重要的那些部分。
代码:https://github.com/owenliang/java-generic-demo
定义普通类
Person是基类,Student继承Person,ClassLeader继承Student。
1 2 3 4 |
package cc.yuerblog; public class Person { } |
1 2 3 4 |
package cc.yuerblog; public class Student extends Person { } |
1 2 3 4 |
package cc.yuerblog; public class ClassLeader extends Student{ } |
多态1
子类Student向上转换为基类Person,这个是面向对象基础。
1 2 3 4 5 6 7 |
// 多态1 private static void demo1() { Student a = new Student(); // 正确 Person b = a; } |
多态2
1 2 3 4 5 6 7 |
// 多态2 private static void demo2() { Student[] a = new Student[5]; // 正确 Person[] b = a; } |
上述Student[]可以赋值给Person[],在C++里需要循环逐个赋值,而java竟然可以整体转换比较新奇。
泛型1
1 2 3 4 5 |
// 泛型1 private static void demo3() { List<Student> a = null; List<Person> b = a; // 错误:List<Student>和List<Person>是两个不同的类型,不受Student与Person本身多态影响 } |
虽然Student可以向上转换为Person,但是List<Student>是不能向上转换为List<Person>的,这完全是两个不同的数据类型。
泛型2
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 泛型2 private static void demo4() { List<Student> a = null; List<?> b = a; // 错误:编译器不知道?原本是什么类型,所以添加任何元素都是禁止的 b.add(new Student()); b.add(new Person()); b.add(new Object()); // 正确,无论?原本是什么类型,Object都是基类,符合多态向上转换 Object o = b.get(0); } |
?用于通配类型,因此List<?> b可以接纳任意类型的List。
因为List<?> b里面的数据类型是不明确的(虽然我们眼睛看得出来,但这并没有什么卵用),所以向b内添加任何类型的对象都是不允许的。
同样道理,从List<?> b取出来的元素类型也不明确,我们只能确信它可以向上转换为Object基类。
泛型3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 泛型3 private static void demo5() { List<Student> a = null; // 正确:?通配任意的Person子类 List<? extends Person> b = a; // 报错:虽然?是Person的某个子类,但是我们并不知道List中的真实数据类型,因此禁止添加元素 b.add(new Student()); b.add(new Person()); // 正确:无论?是Person的哪个子类,它们都以Person为基类 Person o = b.get(0); } |
List<? extends Person> b对?通配符进行了进一步约束,即泛型类型必须是Person的子类,比如List<Student>就满足该通配约束。
面对List<? extends Person> b来说,其数据类型已经不明确了,唯一可以确信的是该类型继承自Person,也就是说它可能是List<Student>、List<ClassLeader>或者其他我们不知道的Person子类。
在这种对List数据类型不完全明确的情况下,会存在这些可能的case:
- 如果List<? extends Person> b实际类型是List<Student> b,那么我们add(new ClassLeader())是没有问题的。
- 但如果List<? extends Person> b实际类型是List<ClassLeader> b,那么我们add(new Student())肯定是编译不过的。
当我们用?把编译器的眼睛蒙上之后,其实编译器也不知道实际情况是哪个case,所以就干脆都不让add好了。
get容易理解,因为List<? extends Person>约束了?必须是Person的子类,所以取出来的对象可以向上转换为Person(任何Person的父类型当然也可以)。
泛型4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 泛型4 private static void demo6() { List<Person> a = null; // 正确:?通配Student的任意父类 List<? super Student> b = a; // 正确:?通配任意Student父类,因此Student和ClassLeader都符合多态向上转换 b.add(new Student()); b.add(new ClassLeader()); // 错误:?通配Student父类,但Student父类不一定只有Person,所以<? super>没法add父类的实例 b.add(new Person()); // 正确:?通配Student任意父类,但是?也不知道具体是哪个,所以只能多态向上为Object Object o = b.get(0); } |
List<? super Student> b 对通配符?进行了进一步约束,即泛型类型必须是Student的父类,这里就是Person和Object满足条件。
因为List<? super Student> b的实际类型一定是Student的父类型,因此所以继承自Student的子类都可以通过向上转换类型被add到b列表内。
我们可以枚举一些case:
- 假设List<? super Student> b实际类型是List<Person>,那么new Student()和new ClassLeader()都可以向上转换类型完成add。
- 如果Student有2个父类,除了extends Person之外还implements了Child接口,那么:
- 假设List<? super Student> b实际类型是List<Person>,那么new Person()是没有问题的。
- 假设List<? super Student> b实际类型是List<Child>,那么new Person()去add肯定编译不过,因为Person和Child是平行关系,虽然它俩都是Student的父类。
你会发现上述加粗的部分是<? super Student>无法明确的2种case,因此当我们向List<? super Student>添加任意Student的父类对象是一定编译不过的,因为不明确。
但是添加Student的任意子类都是OK的,因为它们一定可以向上转换为Student的任意父类型。
get容易理解,因为List<? super Student>一定是Student的父类型,但是也不明确到底是哪个父类型,所以get的时候只能用Object接住。
总结
1 2 3 4 5 |
public static void main(String[] args) { // 总结一下: // 1,<? extends A> 通配A的子类,禁止任意add,可以get为A类 // 2,<? super A> 通配A的父类,可以add任意A的子类对象,可以get为Object } |
我们应该按实际需求考虑用什么语法:
- 通配符?如果不做任何约束,那么它是完全不明确的,不能往里add任何东西,取出来的只能当做Object。
- 通配符<? extends A>只接受A类的子类,但是因为不明确具体是哪个子类,所以add不了任何东西,但是get出来都可以向上转换为A类。
- 通配符<? super A>只接受A类的父类,但是因为不明确具体是A的哪个父类,所以只能add A或者A的子类,因为它们可以都向上转换为A的任意父类,同时get因为不明确是A的哪个父类所以只能向上转换为Object来接收。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~
