意义:编写的代码可以对多种不同的类型对象重用!
带着疑问看泛型
-
为什么引入泛型?
-
适用于多种类型执行相同的方法,不需要对每一个数据类型都重载方法。
-
类型安全,通过在使用时指定类型,避免了类型转化可能发生的错误;有了泛型,就能告诉编译器具体是哪个类型,编译器自动检查;相当于把运行时可能的错误移到了编译时出现错误。
-
-
为什么有了泛型类,还引入了泛型方法?
-
泛型类在实例化的时候,就指定了相关类型,如果想换另一种类型就必须得重新new,这样就不够灵活;引入泛型方法,其在调用的时候才指定相应类型,就很灵活了。
-
1、泛型的定义使用
-
泛型类
-
泛型接口
-
泛型方法:是指在调用方法时,指定具体类型
2、泛型的限定
-
上界通过 extend 关键字实现。其中Comparable就是T的上界,传入的类型必须实现Comparable接口
-
下界通过super关键字实现。
// 类型的限定 --> 类型T必须实现Comparable接口才能比较
public static <T extends Comparable<T>> T min(T[] a) {
if (a == null || a.length == 0) return null;
T smallest = a[0];
for (int i = 1; i < a.length; i++) {
if (smallest.compareTo(a[i]) > 0) smallest = a[i];
}
return smallest;
}
3、泛型和虚拟机(编译器在编译期间自动插入类型转化相关字节码指令)
3.1 泛型的擦除
Java的泛型是”伪泛型“,只是在语法上支持,在编译的时候会泛型擦除,把泛型替换成具体类型(Object或限定类型)。
3.2 转换泛型表达式
编写一个泛型方法调用时,如果擦除了返回类型,编译器会插入强制类型转换。也就是说实际上编译器把方法调用转换为两条虚拟机指令:
-
对原始方法的调用;
-
将返回的类型强制转换为对应泛型参数类型;
3.3 转化泛型方法
泛型的擦除也会发生在泛型方法当中,当发生泛型擦除后可能与多态发生冲突,为解决这个问题,编译器会生成一个桥方法。
4、泛型的限制与局限性
-
不能使用基本数据类型实例化泛型的类型参数。例如:类型擦除后,Object不能存储int值。
-
运行时,类型的查询只适用于原始类型。 即:
Test<String> t1 = ...;
Test<Integer> t2 = ...;
if(t1.getClass() == t2.getClass()) // True
-
不能创建参数化类型的数组。因为数组一旦创建就会记住其类型
5、泛型类型的继承规则
-
泛型类继承原型类,但泛型类与泛型类之间没有关。如:ArrayList<String> is ArrayList ;ArrayList<String> 与ArrayList<Intrger> 没有关系。
6、通配符
7、泛型类和泛型接口代码例子
/**
* 泛型类
*/
public class GenericsTest<T> {
// 泛型类可以和泛型方法一起用
// 有了泛型方法后,增加了泛型方法的灵活性
private T c;
public <E> E getObject(Class<E> type) throws InstantiationException, IllegalAccessException {
return type.newInstance();
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class<? extends LinkedList> type = LinkedList.class;
GenericsTest<Integer> test = new GenericsTest<>();
LinkedList list = test.getObject(type);
list.add("123");
}
}
/**
* 泛型接口
*/
// 接口中的字段都是 public static final; 方法默认 public; java 8 之后支持默认方法和静态方法
interface GenericsSetI<T> {
T getObject();
}