泛型
1 概述
推出泛型之前,可以构建一个元素类型为Object的集合,可以存储任意数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,后则很容易引发ClassCastException
Java泛型(generics)是JDK5中引入新特性,泛型提供了编译时类型安全监测机制,可以允许我们在编译时监测到非法类型的数据结构
泛型本质就是类型参数化,也就是所操作的数据类型被指定为一个参数,这样类型安全、消除了强制类型的转换
2 泛型类
【格式】
class 类名 <泛型标识1, 泛型标识2, ...> {
private 泛型标识 变量名;
...
}
【示例】
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Generic<T> {
private T key;
}
常用的泛型标识:T、E、K、V
-
在创建泛型对象时,不支持基本数据类型,需要使用各自的包装类型
-
在创建泛型对象时,如果不指定类型,默认使用Object类型
-
泛型类在逻辑上可以看成是多个不同的类型,实际上是相同类型
public static void demo1() { Generic<String> strGeneric = new Generic<>(); Generic<Integer> intGeneric = new Generic<>(); // 打印:true System.out.println(strGeneric.getClass() == intGeneric.getClass()); }
2.1 泛型类派生子类
-
子类也是泛型类,子类和父类的泛型要一致(倘若子类进行泛型扩展,子类也要有一个泛型与父类一致)
class ChildGeneric<T> extends FatherGeneric<T> {} // ok class ChildGeneric<T, E, K, V> extends FatherGeneric<T> {} // no class ChildGeneric<E, K, V> extends FatherGeneric<T> {}
-
子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String> {}
3 泛型接口
interface 接口名称 <泛型标识1, 泛型标识2...> {
泛型标识 方法名();
}
-
实现类不是泛型类,接口要明确数据类型
-
实现类也是泛型类型,实现类和接口的泛型类型一致
4 泛型方法
泛型类:在实例化的时候指明泛型的具体类型
泛型方法:在调用方法的时候指明泛型具体类型
访问修饰符 <T, E, ...> 返回值类型 方法名(形参列表) {
方法体...
}
泛型方法的泛型,与泛型类的泛型,无关系
注意区分泛型方法,和成员方法。如果static方法要使用泛型能力,就必须使其成为泛型方法
public <T> void print(T...list) { // 泛型可变参数
for(T t : list) {
System.out.println(t);
}
}
5 类型通配符
5.1 引子
class Box<T> {
@Getter
@Setter
private T first;
}
public class Test {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
showBox(box1);
Box<Integer> box2 = new Box<>();
showBox(box2);
}
public static void showBox(Box<Number> box) {
Number first = box.getFirst();
System.out.println(first);
}
// 这是重载?????
public static void showBox(Box<Integer> box) {
Number first = box.getFirst();
System.out.println(first);
}
}
上面的代码是有问题的,出在了
showBox(box2)
身上,因为参数类型不匹配,需要的是Box<Number>
,而box2的类型是Box<Integer>
。那如何解决问题?再写一个接收Box<Intger>
类型的同名showBox方法,进行重载?
这是不行的。虽然Box<Number>
和Box<Integer>
看似类型不同,实则是同一类型,重载的话,要求参数类型不同,显然不符合重载要求。
那么如何解决???
5.2 类型通配符
// ?即为类型通配符
public static void showBox(Box<?> box) {
Object first = box.getFirst();
System.out.println(first);
}
- 类型通配符一般使用
?
代替具体的类型实参 - 类型通配符是类型实参,而不是类型形参
5.3 类型通配符的上限
类/接口 <? extends 实参类型>
// 通过? extends Number 表明类型通配符的上限
public static void showBox(Box<? extends Number> box) {
Number first = box.getFirst();
System.out.println(first);
}
? 代表任意类型,但任意类型范围太广,倘若我们知道各式各样的泛型类型的上限,那么可以通过上述的写法加以限定。
比如,我可能创建一堆的Box对象,传递的泛型的类型,可能是Integer、Float、Double、Long或者Number类型,虽然可以统一用通配符?
一招解决,但同样会存在String类型的泛型也会被接受。
我现在不想String或者其他非数字类型的Box去执行showBox的方法,这时会发现Integer、Float、Double、Long都有统一的父类Number,那么就可以使用上面的写法进行限定:只有泛型类型是Number类型,及Number子类可以使用这个方法
使用泛型通配符上限,对于集合是无法填充元素的
// MiniCat extends Cat,Cat extends Animal
public static void showAnimal(ArrayList<? extends Cat> list) {
list.add(new Cat());
list.add(new MiniCat());
list.add(new Animal());
}
上面的三行代码都不允许。因为该ArrayList的泛型上限是Cat,并没有具体指明是Cat还是MiniCat还是其他Cat的子类,故而无法进行如此确定的添加
5.2 类型通配符的下限
类/接口 <? super 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的父类
以TreeSet为例
// 省略toString和构造
class Animal {
String name;
}
// 省略toString和构造
class Cat extends Animal{
Integer age;
}
// 省略toString和构造
class MiniCat extends Cat{
Integer level;
}
class Comparator1 implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.name.compareTo(o2.name);
}
}
class Comparator2 implements Comparator<Cat> {
@Override
public int compare(Cat o1, Cat o2) {
return o1.age - o2.age;
}
}
class Comparator3 implements Comparator<MiniCat> {
@Override
public int compare(MiniCat o1, MiniCat o2) {
return o1.level - o2.level;
}
}
public static void demo1() {
TreeSet<Cat> treeSet1 = new TreeSet<>(new Comparator1());
TreeSet<Cat> treeSet2 = new TreeSet<>(new Comparator2());
TreeSet<Cat> treeSet3 = new TreeSet<>(new Comparator3());
}
从图中可以知道,TreeSet的构造方法传递的Comparator泛型下限是E,在上面代码的示例中E是Cat类,这就意味着treeSet3这一行是有误的。
6 类型擦除
泛型是JDK1.5引进的概念,泛型代码能够很好的和之前版本的代码兼容。那是因为,泛型信息只存在于代码的编译阶段,进入JVM之前,与泛型相关的信息会被擦除,称之为类型擦除
6.1 无限制类型擦除
class Box<T> {
private T first;
}
public static void demo2() {
Box<Integer> box = new Box<>();
Class<? extends Box> boxClass = box.getClass();
Field[] fields = boxClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + ":" + field.getType().getSimpleName());
}
}
6.2 有限制的类型擦除
class Box<T extends Number> {
private T first;
}
// 测试代码不变
6.3 擦除方法中类型定义的参数
class Box<T extends Number> {
private T first;
public E getFirst() { return first; }
public void setFirst(E first) { this.first = first; }
public <T extends List> T show(T t) { return t; }
}
public static void demo3() {
Box<Integer> box = new Box<>();
Class<? extends Box> clz = box.getClass();
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + ":" + method.getReturnType().getSimpleName());
}
}
6.4 桥接方法
7 泛型数组
泛型数组的创建:
- 可以声明带泛型的数组引用,但不能直接创建带泛型的数组对象
// 不可行
ArrayList<String>[] arr = new ArrayList<String>[5];
// 可行
ArrayList<String>[] arr;
// 可行
ArrayList<String>[] arr = new ArrayList[5];
- 可以通过
java.lang.reflect.Array的newInstance(Class<T>, int)
创建T[]数组
class Fruit<T> {
// 不可行
private T[] arr = new T[5];
}
class Fruit<T> {
private T[] arr;
public Fruit(Class<T> clz, int len) {
// 创建泛型数组
arr = (T[]) Array.newInstance(clz, len);
}
}