啥是泛型?
泛型的本质是参数化(可变)类型,定义时给类型指定一个参数,使用时再指定此参数的具体的类型。
这种类型可以用在类、接口、方法中,分别称为泛型类、泛型接口、泛型方法。
为啥要引入泛型?
代码复用。
话不多说 直接举例:
private static int add(int a, int b){
return a + b;
}
private static float add(float a, float b) {
return a + b;
}
没有泛型,每种数据类型都要一个方法。有了泛型,可以复用为一个方法,灰常方便。
private static <T extends Number> double add(T a, T b) {
return a.doubleValue() + b.doubleValue();
}
泛型的基本使用
泛型类
class Node<T> { // 泛型符号、可以任意字符, 也可以多个
private T value ;
public T getValue(){
return value ;
}
public void setValue(T value){
this.value = value ;
}
}
泛型接口
interface Node<T>{ // 在接口上定义泛型、同泛型类相似
public T getValue() ;
}
泛型
public <T> T getObject(Class<T> clazz) { // 简单来说就是<> 放在方法前面
T t = clazz.newInstance();
}
泛型的上下限
在使用泛型的时候,可以为传入的泛型类型实参上下边界做限制,如:类型实参只准传入某种类型的父类或某种类型的子类。
泛型上限
class Node<T extends Number>{ // 此处泛型只能是数字类型
private T value ;
泛型下限
class Node<T>{
private T value ;
}
class Demo {
public static void fun(Node<? super String> node){ // 只能接收String或者其父类
System.out.print(node) ;
}
}
泛型擦除的一些小问题
是指在编译器处理带泛型定义的类、接口或方法时,会在字节码指令集里抹去全部泛型类型信息,泛型被擦除后在字节码里只保留泛型的原始类型(raw type)。
简单来说:编译后的字节码里面 不存在泛型的信息。
如何证明泛型擦除
原始类型相等
ArrayList<String> list1 = new ArrayList<String>();
ArrayList<Integer> list2 = new ArrayList<Integer>();
System.out.println(list1.getClass() == list2.getClass()); // true
通过反射添加其他类型元素
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1); //这样调用 add 方法只能存储整形,因为泛型类型为 Integer
list.getClass().getMethod("add", Object.class).invoke(list, "aaa");
泛型的多态?
类型擦除会造成多态的冲突,而JVM解决方法就是桥接方法。
// 父类
class Node<T> {
private T value ;
public T getValue(){
return value ;
}
public void setValue(T value){
this.value = value ;
}
}
// 子类
class IntegerNode extends Node<Integer> {
private Integer value ;
@Override
public Integer getValue(){
return value ;
}
@Override
public void setValue(Integer value){
this.value = value ;
}
}
看起来相当之合理,编译器也不报错,但是问题来了,从泛型擦除我们知道,父类的T会变成Object。父类的类型是Object,子类的类型是Integer,这不是重写,这是重载!
如果是重载,那么应该是子类继承public void setValue(Object value)方法,然后重写一个public void setValue(Integer value)方法,但是子类setValue方法传Object对象时,会发生编译错误。
难道子类IntegerNode继承父类Node的时候,父类中泛型就会从Object变成Integer?
--不可能,绝对不可能。反例:如果一个类被多个类继承,父类泛型都乱套了。
JVM桥接方法
将IntegerNode反编译,其实发现有4个方法,以setValue举例
public void setValue(java.lang.Integer); // 子类重写的方法
public void setValue(java.lang.Object); //编译器生成的桥接方法
可以看到桥方法的参数类型都是Object,而桥接方法的内部实现,就只是去调用我们自己重写的那两个方法。
也就是说,子类中真正重写的父类是我们看不到的桥接方法。我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。
所以,虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。
如何获取泛型的参数类型?
可以通过反射(java.lang.reflect.Type
)获取泛型
//getActualTypeArguments 返回确切的泛型参数, 如Map<String, Integer>返回[String, Integer]
Type type = ((ParameterizedType) clazz).getActualTypeArguments()[0];
System.out.println(type);//class java.lang.String