Java基础之泛型总结

一、为什么使用泛型

1.可读性好,一看泛型就知道里面装的什么类型的元素
2.泛型只在编译前存在,编译后会将类型擦除并替换成其限定类型,编译后就是一个普通类。泛型可以在编译前规范代码,防止放入一些不合格的对象导致运行期报错,提高程序健壮性。
3.使用泛型让程序更灵活,各类框架就大量使用泛型

个人理解:泛型可以大概分为两部分:定义泛型和使用泛型,大多数时候我们是在使用泛型,也就是用具体类型限制,比如:List《String》,而写一些通用工具或者框架的时候需要自己定义泛型类,泛型方法等提高程序的灵活性。

小小牛博客

二、定义简单泛型类

例如:

定义泛型类:

1
2
3
4
5
6
7
8
9
10
public class Pair<T>{
private T first;
private T second;
public Pair(){first = null;second = null}
public pair(T first, T second){
this.first = first;
this.second = second;
}
//set get 略...
}

类型变量使用大写形式,且比较短,在java库中,E表示集合的元素类型,K和V分别表示Key和值,T表示任意值,也可以使用邻近的U和S

使用泛型:

1
Pair<String>

三、泛型方法

例:

定义泛型方法

1
2
3
4
5
public ArrayAlg {
public static <T> T getMiddle(T... a){
return a[a.length / 2];
}
}

泛型的类型变量放在修饰符的后面(这里是public static) ,返回值的前面,泛型方法可以定义在可以定义在普通类中,也可以定义在泛型类中

使用泛型方法

1
String middle = ArrayAlg.<String>getMiddle("aaa","sss","sdsd");

上面限制了是String,如果放入一个int类型的参数就会报错,大多数情况下《String》可以省略的,编译器会自己推导出限制类型;

1
ArrayAlg.getMiddle("aaa",123,"sdsd");

上面这句代码不会报错,但是编译器推导的限制类型是String 和 Integer公共父类型,如果用String接收返回值会提示:

1
Incompatible types. Required String but 'getMiddle' was inferred to T: Incompatible types: Serializable & Comparable<? extends Serializable & Comparable<?>> is not convertible to String

大概意思是可以赋值给Serializable 或 Comparable类型

四、类型变量的限定

  • 我们在定义泛型的时候可以限定其范围:
    如:
    1
    public static <T extends Comparable> T min(T[] a)...

我们在使用时就只能使用实现了Comparable接口的类型来限制。

这里是接口但依然使用extends关键字,主要是为了表明是父子关系。不管是类还是接口泛型里都是使用extends

  • 一个类型变量或通配符可以有多个限定
    如:
    1
    T extends Comparable & Serializable

可以根据需要拥有多个接口超类型,但是限定中至多有一个类,而且这个类必须在列表的第一个

1
T extends class & interface & interface & interface //就像这样的格式,如果有类,放在第一个

五、类型擦除

无论何时定义一个泛型类型,都自动提供一个原始类型,原始类型用第一个限定的类型变量来替换

T 是一个无限定的变量,所以会直接用Object替换,如 private T first 编译后变成 private Object first

如果是

1
<T extends Comparable & Serializable>

则原始类型会用第一个(即Comparable)代替,private Comparable first;

在使用泛型的时候编译器会自动加入强制类型转换,转换成我们给定的类型

如:

1
2
Pair<String> b = ...
String ru = b.getName();

类型擦除后getName返回类型会用Object替换T,编译器会插入String强制类型转换

注意:要考虑类型擦除后带来的隐藏隐患

六、约束与局限性

1、不能用基本类型实例化类型参数

1
如:Pair<int>不行,只能用Pair<Integer>

2、运行时类型查询只适用于原始类型

1
2
例如:if(a instanceof Pair<String>) //报错
( Pair<String>) a //强转,报错

3、不能创建参数化类型的数组

1
Pair<String>[] table = new Pair<String>[10];//报错,声明不会报错,但初始化就会报错

4、不能实例化类型变量

如:new T[]

5、不能构造泛型数组

如:T[]

6、泛型类的静态上下文中类型变量无效

如:private static T abc;//报错

7、不能抛出或捕获泛型类的实例

如:

1
2
3
4
5
try{

}catch(T e){

}

不过,在异常规范中使用类型变量是允许的

1
2
3
4
5
6
7
8
9
public static <T extends Throwable> void doWork(T t) thows T{//ok
try{

}catch(Throwable realCause){
t.initCause(realCause);
throw t;
}

}

8、注意泛型的继承

1
2
 A extends B
Pair<B> 不是 Pair<A> 的子类,会类型擦除的

七、通配符类型

能一定程度解决上面泛型不能继承的问题

1
Pair<? extends B>

如果调用上面方法的set方法会报错,不能设置值,因为不能确定是什么类型的值,可以get

  • ? extends B 不能设置值,如果是集合(如List)就不能添加值。因为不能到是什么类型的值,但是能获取值,获取的值一定是B的子类,可以用B接收

  • ? super B 不能获取值,能设置/添加值,因为一定是B的子类,不能获取值,如果获取出来用什么接收呢?因为类型不确定。

  • ? 不能添加也不能获取,道理同上,但是能加入null,因为对象都能表示为null

八、反射和泛型

未完。。