1.什么是泛型
1.泛型本质是指类型参数化, 允许在定义类、接口、方法时使用, 当使用时指定具体类型
2.所有使用该泛型参数的地方都被统一化 , 保证类型一致, 如果未指定具体类型, 默认是Object
类型
3.集合体系中的所有类都增加了泛型, 泛型也主要用在集合
2.基础
-
泛型类
public interface Node<T> { public T next(); }
总结:
- 泛型的类型不能是简单类型。
instanceof
操作的泛型对象不能是具体类型
-
泛型方法
public class Test1<T> { // 与类所声明的类型一致 private T t; // 此方法不是泛型方法, 因为类型已经在初始化类时已经确定 // 此方法的入参与类所声明的类型一致 private void test1(T t) { } // 此方法是泛型方法 // 此方法的入参与类所声明的类型不一致, 即此处的T为任意类型 private <T> void test2(T t) { } // 如果该方法为静态的, 则必须需要定义为泛型方法 private static <T> void test3(T t) { } }
public class Test2 { // 此方法是泛型方法, T为任意类型 private <T> void test1(T t) { } }
总结:
- 在调用方法的时候指明泛型的具体类型即为
泛型方法
- 泛型类中, 存在方法入参使用泛型且被定义为静态, 则该方法必定为泛型方法
- 入参"T"只是个标识, 26个字母都可使用且可以任意组合, 但应尽量具有含义
- 在调用方法的时候指明泛型的具体类型即为
-
泛型类派生出的子类
// 定义上述接口泛型实现类 public class SubNode<T> implements Node<T>{ @Override public T next() { return null; } }
// 定义上述接口具体实现类 // 此时T将指定为具体类型String public class SubNode implements Node<String> { @Override public String next() { return ""; } }
-
泛型数组
-
不能创建一个确切的泛型类型的数组, 除非是通配符, 即:
// X List<String>[] arr1 = new ArrayList<String>[1]; // ✔ List<?>[] arr2 = new ArrayList<?>[1]; // 同时下面这种方式也是可行的 List<String>[] arr3 = new ArrayList[1];
-
具体原因可查看官方说明:
// 例如可以使用此声明方式 List<String>[] lsa = new List[1]; Object o = lsa; Object[] oa = (Object[]) o; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); // 不可靠的, 但通过了运行时存储检查 oa[1] = li; // 运行错误: ClassCastException String s = lsa[1].get(0);
-
获取泛型数组的一种实现法式
// 配合函数式实现灵活 // 不会出现ClassCastException, 可以由具体类型数组接收 T[] targetArr = (T[]) Array.newInstance(T类型Class, 数组长度);
static class Test { public <T> T[] getArray(T t) { Object[] array = new Object[1]; array[0] = t; return (T[]) array; } public static void main(String[] args) { Test test = new Test(); // 控制台输出[测试] System.out.println(Arrays.toString(test.getArray("测试"))); // 不能通过 // Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String; String[] array = test.getArray("测试"); System.out.println(Arrays.toString(array)); } }
-
-
类型通配符(
?
)public class Test3<T> { // Test3<Integer>, Test3<String>实例都可作为入参 public void test1(Object t){ } // Test3<Integer>, Test3<String>实例都可作为入参 public void test2(Test3<?> t){ } // Test3<Integer>, Test3<String>实例不可作为入参, 编译报错 public void test3(Test3<Object> t){ } }
总结:
- 当泛型的具体类型不确定时,可使用
?
替代,?
可看做所有泛型的超类 - 如不使用泛型类, 可使用
Object
类
- 当泛型的具体类型不确定时,可使用
3.上限和下限
泛型上限
-
格式: 类型名称
<? extends 类>
对象名称 -
意义: 只能接受该类及其子类型
-
不影响往外取, 不能往里存
/** * [读取] * 如果要从集合中[读取]类型T的数据, 并且[不能写入], 可以使用? extends通配符(Producer Extends) * 例如: List<? extends Animal> animalList, 动物、狗、猫、猪、鸡... 只要是动物, 都可被存入进 */ public static void testAnimal1() { List<Dog> dogList = Arrays.asList(new Dog()); List<? extends Animal> animalList = dogList; /** * animalList是一个Animal的子类的List, 由于Dog是Animal的子类 * 因此将dogList赋给animalList是合法的, 但是编译器会阻止将new Cat()加入animalList * 因为编译器只知道animalList是Animal的某个子类的List, 但并不知道究竟是哪个子类, 为了类型安全, 只好阻止向其中加入任何子类, 并且不可以加入new Animal() * 事实上, 不能够往一个使用了? extends的数据结构里写入任何的值 */ // 编译失败 // animalList.add(new Cat()); // 编译失败 // animalList.add(new Animal()); // 编译失败 // animalList.add(new Dog()); /** * 由于编译器知道它总是Animal的子类型, 但并不知道具体是哪个子类, 因此我们总可以从中读取出Animal对象 */ Animal animal = animalList.get(0); Object obj = animalList.get(0); // 编译失败 // Dog dog = animalList.get(0); }
泛型下限
-
格式: 类型名称
<? super 类>
对象名称 -
意义: 只能接受该类及其父类型
-
不影响往里存, 往外取只能存放在Object对象里
/** * [写入] * 如果要往集合中[写入]类型T的数据, 并且不需要[读取], 可以使用? super通配符(Consumer Super) * 如果既要存又要取, 就不要使用任何通配符 */ public static void testAnimal2() { List<Animal> animalList = new ArrayList<>(); List<? super Dog> dogList = animalList; /** * 这里的animalList是一个Animal的超类(父类, superclass)的List * 同样地, 出于对类型安全的考虑, 我们可以加入Dog对象或者其任何子类(如WhiteDog)对象 * 但由于编译器并不知道List的内容究竟是Dog的哪个超类, 因此不允许加入任何超类类型 */ dogList.add(new Dog()); // dogList.add(new WhiteDog()); // 编译失败 // dogList.add(new Animal()); // 编译失败 // dogList.add(new Object()); /** * 当我们读取的时候, 编译器在不知道是什么类型的情况下只能返回Object对象, 因为Object是所有Java类的祖先类 */ Object obj = dogList.get(0); // 编译失败 // Dog dog = dogList.get(0); // 编译失败 // Animal animal = dogList.get(0); }
4.类型擦除与桥接方法
泛型是提供给
javac
编译器使用的, 它用于限制输入类型, 让编译器在源代码级别上挡住输入的非法数据
但编译器编译完带有泛形的java
程序后, 生成的class
文件中将不再带有泛型信息, 以此使程序运行效率不受到影响, 这个过程称之为"擦除"
。
由于类型被擦除了, 为了维持多态性, 所以编译器就自动生成了桥接方法
。
/**
* 桥接方法
* 1.类型擦除: javac -> class泛型被擦除了
* 2.泛型虽然被擦除了,但是一定要保留泛型的行为
*/
/**
* 泛型类
*/
public class Node<T> {
public T data;
public void setData(T data) {
this.data = data;
}
}
class SubNode extends Node<Integer> {
@Override
public void setData(Integer data) {
this.data = data;
}
}
/**
* 被擦除后的泛型类
*/
public class Node {
public Object data;
public void setData(Object data) {
this.data = data;
}
}
class SubNode extends Node {
// 首先请求该方法
@Override
public void setData(Object data) {
setData((Integer) data);
}
// 然后内部调用桥接方法, 用于对入参类型的限制
public void setData(Integer data) {
super.setData(data);
}
}