泛型
注:不管是类还是方法,只要使用泛型就必须先定义;
方法定义就是再返回类型前加上<T>
,类就是再方法名后加<T>
泛型的继承
泛型在继承时,可以明确父类(泛型类)的参数类型,也可以不明确。![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/59c94d0139e7a5b6ebb48c0765f4ce6f.png)
(1)明确
// 在实现泛型类时明确父类的类型。
(2)不明确
子类不去明确类型,明确类型的工作留在创建对象的时候。
类型通配符
(1) 无界 <?>
感觉加和不加也没啥区别是吗?其实不然,存在即合理嘛!
那么为什么不加也可以用呢?因为为了兼容,不加的话就是失去泛型的安全性和表现力。Java即将进入第二个十年,泛型被加进来时,还存在大量不使用泛型的代码。保证所有这些代码合法并与使用泛型的新代码兼容是关键的。将参数类型的实例传递给设计用于原始类型的方法必须是合法的。
(2) 上届 <? extends PeronOfSchool>
注:能够接收A类或者A类的子类,当我们使用extends时,我们可以读元素(方法和属性),因为元素都是A类或子类,可以用A类拿出。
(3) 下届 <? super Teacher>
当使用super时,可以添加元素,因为都是A类或父类,那么就可以安全的
插入A类。
类型擦除
- 为了保持兼容性,Java的泛型其实是一种伪泛型,这是因为Java 在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在 生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
- 如在代码中定义SuperArray 和SuperArray 等类型,在编译后都会变成SuperArray ,JVM看到的只是SuperArray,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现完全避免类型转换异常的情况。
(1)泛型不能是基本数据类型(极其重要)
不能用类型参数替换基本类型。就比如,没有SuperArray ,只有SuperArray 。因为当类型擦除后, SuperArray 的原始类型变为Object ,但是Object 类型不能存储double 值,只能引用Double 的值。
(2)重载方法
泛型被擦除后,其实这两个方法是一致的,并不能构成泛型。
(3)类型擦除和多态的冲突
注:在多态中方法的重写会因为类型擦除存在冲突,但是jvm提供了中间的解决方法。
泛型类:
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
继承泛型类:
package com.ydlclass;
import java.util.Date;
public class DatePair extends Pair<Date>{
@Override
public Date getValue() {
return super.getValue();
}
@Override
public void setValue(Date value) {
super.setValue(value);
}
}
类型擦除后父类会变成下列所示:
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
先来分析setValue 方法,父类的类型是Object ,而子类的类型是Date ,参数类型不一样,这如果是在普通的继承关系中,根本就不会是重写,而是重载。
如果是重载,那么子类中两个setValue 方法,一个是参数Object 类型,一个是Date 类型,可是我们发现,根本就没有这样的一个子类继承自父类的Object类型参数的方法。所以说,确实是重写了,而不是重载了。
原因:从编译的结果来看,我们本意重写setValue 和getValue 方法的子类,竟然有4个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的**【桥方法】,我们从字节码中看到两个标志【ACC_BRIDGE,ACC_SYNTHETIC】**。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而在我们自己定义的setvalue 和getValue 方法上面的@Override 只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。所以,虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。
并且,还有一点也许会有疑问,子类中的桥方法Object getValue()和Date getValue() 是同时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分辨这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,编译器为了实现泛型的多态允许自己做这个看起来
“不合法”的事情,然后交给虚拟机去区别。
静态方法和静态类中的问题
泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数:
eg:
public class Test2<T> {
public static T one; //编译错误
public static T show(T one){ //编译错误
return null;
}
}
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。
但是:
public class Test2<T> {
public static <T> T show(T one){ //这是正确的
return null;
}
}
因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的 T,而不是泛型类中的T。就是在此静态方法中又重新定义了一次T,所以此处的T跟类中的T是两回事,在调用此泛型方法时,此泛型静态方法中的T就会被明确,所以是合法的。