什么是泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
实际上,泛型的本质是参数化类型,即把类型当成参数一样传递,可以在程序运行的时候动态的修改它的类型,泛型可以定义在类、接口、方法上,分别被称为泛型类,泛型接口,泛型方法。
为什么需要泛型
泛型能够保证在编译时检查类型安全
没有使用泛型之前,我们使用List集合在向其加入的每一个元素我们都需要进行强制类型转化,以确保加入的类型时相同的。
不使用泛型
public static void main(String[] args) {
List stringList=new ArrayList<>();
stringList.add("tom");
stringList.add(new A());
}
//编译正常
使用泛型
public static void main(String[] args) {
List<String> stringList=new ArrayList<>();
stringList.add("tom");
stringList.add(new A());
}
//编译不通过
使用泛型可以在编译时候就告诉编译器我们需要什么类型的变量,让编译器来做类型安全检查,让程序更加安全。
泛型能够让我们程序表达更加高效
我们定义一个类
在定义类的时候我们自己也无法确定属性c的类型是什么,只有在使用的时候才能确定c的类型,或者说这个类的属性c本就是不确定的,我们使用object来表示,但在我们每次使用c属性时,我们都需要进行强制类型转化。
public class B {
private Object c;
public B(Object c) {
this.c = c;
}
public static void main(String[] args) {
B b=new B("tom");
String a= (String) b.c;
}
}
我们使用泛型来参数化类型,这使得我们可以在动态的决定c的类型,不需要我们进行强制类型转化。实际上泛型的底层也是通过强制类型转化,对类型进行动态的修改,只不过有了泛型不需要我们来进行转化。
public class B<T> {
private T c;
public B(T c) {
this.c = c;
}
public static void main(String[] args) {
B<String> b=new B<>("tom");
String a=b.c;
}
}
如何使用泛型
泛型类
只需要在类名后面打上尖括号,在尖括号内书写泛型,如果有多个泛型,用逗号隔开
public class B<T,TV> {
private T c;
private TV d;
public B(T c) {
this.c = c;
}
public static void main(String[] args) {
B<String,Integer> b=new B<>("tom");
String a=b.c;
}
}
泛型方法
必须在修饰符之后,返回类型之前定义泛型。
public <OB> OB methods(){
System.out.println("这是一个泛型方法");
return null;
}
泛型类的写法和泛型类类似。
泛型擦除
泛型擦除是指泛型类在编译时期,所有的泛型消息都会被消除。Java虚拟机在编译泛型类时不会保留泛型的字节码信息,也就是说虚拟机中没有List和List 这个两个类的字节码文件,只有List这个类的字节码文件,而我们在类中使用的泛型信息,都会被替代成Obejct类型。
比如以下这个泛型类
public class B<T,TV> {
private T c;
private TV d;
public B(T c) {
this.c = c;
}
public <OB> OB methods(){
System.out.println("这是一个泛型方法");
return null;
}
}
在编译之后
public class B {
private Object c;
private Object d;
public B(Object c) {
this.c = c;
}
public Object methods() {
System.out.println("这是一个泛型方法");
return null;
}
}
我们也可以通过反射去证明
public static void main(String[] args) {
List<String> stringList=new ArrayList<>();
List<Integer> integerList=new ArrayList<>();
System.out.println(stringList.getClass()==integerList.getClass());
//true
}
代码创建了两个list 实例对象,它们分别有不同的泛型,但是通过getClass获取的Class对象却是相等的,说明在内存之中不存在List和List的class对象。
我们还可以通过一个例子来证明
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<String> stringList=new ArrayList<>();
stringList.getClass().getMethod("add", Object.class).invoke(stringList,1);
}
//编译正常
我们通过反射拿到Method 对象并且对集合添加一个非String类型对象,结果发现编译通过。说明在编译之后,泛型的信息就被擦除了。
泛型擦除带来的问题
自动类型转化
q:既然泛型在编译的时候会被擦除,那么在运行使用的时候为什么不需要做类型转化。
a:我们不用自己去做类型转化,虚拟机帮我们完成了这个工作。
类型擦除与多态冲突
看以下代码
public interface A<T> {
public T getA();
}
public class B implements A<String> {
@Override
public String getA() {
return null;
}
}
类B实现了接口A,但是a经过类型擦除后方法又会改变
public interface A {
public Object getA();
}
B没有继承到A的这个方法,那为何编译能够通过? jvm提供了一种桥接方法,帮我们解决这个问题。