Java 泛型
一、概论
1. 设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器倒是实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5后使用泛型来解决。因为这个适合除了元素的类型不确定,其他部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数就是泛型。Collection, List,ArrayList, 这个就是类型参数,即泛型。
2. 概念
- 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
- 从JDK1.5后,Java引入了“参数化类型(Parameterized type)"的概念,允许我们在创建集合时再指定集合元素的类型,正如List,这表明该 List 只能存储字符串类型的对象。
- JDK 1.5 改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yjSjuZ1X-1617119782413)(C:\Users\Olenz\AppData\Roaming\Typora\typora-user-images\image-20210222112445556.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OBI4LNj4-1617119782439)(C:\Users\Olenz\AppData\Roaming\Typora\typora-user-images\image-20210222112457522.png)]
- Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。
二、集合中使用泛型
ArrayList<Integer> list = new ArrayList<>();//类型判断
list.add(11);
list.add(22);
list.add(33);
list.add(44);
//遍历方式一:
for (Integer i : list) {
//不需要强转
System.out.println(i);
}
//遍历方式二:
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("Tom1",34);
map.put("Tom2",44);
map.put("Tom3",33);
map.put("Tom4",32);
//添加失败
//map.put(33,"Tom");
Set<Entry<String,Integer>> entrySet = map.entrySet();
Iteratro<Entry<String,Integer>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Entry<String,Integer> entry = iterator.next();
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
三、自定义泛型结构
-
泛型的声明
interface List<T>
和class GenTest<K,V>
T是Type缩写。
-
泛型的实例化
一定要在类名后面指定类型参数的值(类型)。如:
List<String> strList = new ArrayList<String>();
Iterator<Customer> iterator = customers.iterator();
-
T只能是个类,不能用基本数据类型填充。但可以使用包装类填充
-
把一个集合中的内容限制为一个特定的数据类型,这就是 generics 背后的核心思想
-
//JDK 1.5 之前 Comparable c = new Date(); System.out.println(c.compareTo("red")); //JDK 1.5 Comparable<Date> c = new Date(); System.out.println(c.compareTo("red"));
体会:使用泛型的主要优点是能够在编译时就检测错误,而不是运行时。
-
-
自定义泛型类
class Person<T> { //使用T类型定义变量 private T info; //使用T类型定义一般方法 public T getInfo() { return info; } public void setInfo(T info) { this.info = info; } //使用T类型定义构造器 public Person() { } public Person(T info){ this.info = info; } }
-
自定义泛型方法
-
泛型方法可以出现再非泛型类中
-
在泛型方法中可以定义泛型参数,此时参数的类型就是传入数据的类型。
-
泛型方法的格式
访问权限 <泛型> 返回类型 方法名(泛型标识 参数名称) 抛出的异常
public class DAO { public <E> E get(int id, E e) { E result = null; return result; } }
-
-
SomeTips
-
泛型类可能有多个参数,此时应将多个参数一起放在见括号内。比如<E1,E2,E3>
-
泛型类的构造器如下: public GenericClass(){}。
错误的:public GenericClass(){}
-
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
-
泛型不同的引用不能相互赋值。
- 尽管在编译时 ArrayList 和 ArrayList 是两种类型,但是,在运行时只有一个 ArrayList 被加载到 JVM 中。
-
泛型如果不指定,将被擦除,泛型对应的类型均按照 Object 处理,但不等价于 Object。(一般讲,泛型使用就使用到底,要么就一直不用>)
-
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
-
JDK 1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
public class GenericTest { public static void main(String[] args) { //1、使用时,类似于Object,但不等同Object ArrayList list = new ArrayList(); //list.add(new Date());有类型转换失败的风险 list.add("hello"); test(list); //ArrayList<Object> list2 = new ArrayList<Object>(); //test(list2);一旦指定Object,编译时会类型检查,必须按照Object处理 } private static void test(ArrayList<String> list) { String str = ""; for (String s : list) { str += s + ","; } System.out.println(str); }
-
在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
//static 的方法中不能声明泛型 public static void show(T t) { } //不能再 try-catch 中使用泛型定义 public void test() { try{ }catch (MyException<T> ex) { } }
-
异常类不能是泛型的
-
不能使用
new E[]
。但可以:E[] elements = (E[])new Object[capacity];
参考: ArrayList 源码中声明:Object[] elementData,而非泛型参数类型数组。
-
父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
-
子类不保留父类的泛型:按需实现
- 没有类型,擦除
- 具体类型
-
子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
class Father<T1, T2> { } // 子类不保留父类的泛型 // 1)没有类型 擦除 class Son<A, B> extends Father{ //等价于class Son extends Father<Object,Object>{ } // 2)具体类型 class Son2<A, B> extends Father<Integer, String> { } // 子类保留父类的泛型 // 1)全部保留 class Son3<T1, T2, A, B> extends Father<T1, T2> { } // 2)部分保留 class Son4<T2, A, B> extends Father<Integer, T2> { }
-
结论:子类必须是 “富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。
-
四、泛型在继承上的体现
-
类A 是类B 的父类,G 和 G 二者不具备子父类关系,二者为并列关系。
比如:String是Object的子类,但是List并不是List
的子类。public void testGenericAndSubClass() { Person[] persons = null; Man[] mans = null; // 而 Person[] 是 Man[] 的父类. persons = mans; Person p = mans[0]; // 在泛型的集合上 List<Person> personList = null; List<Man> manList = null; // personList = manList;(报错) }
五、通配符的使用
-
类型通配符:?
如:List<?> , Map<?, ?>
-
读取 List<?> 的对象 list 中的元素时,永远是安全的,因为不管 list 的真实类型是什么,它包含的都是 Object 。
-
无法向 list 中写入元素,因为我们不知道 c 的元素类型(类型不安全0),方法 add() 的形参类型 E 为集合的元素类型,我们传给 add 的任何方法必须是某一类型的子类。
- 唯一例外时 null,它是所有类型的成员。
-
可以调用 get() 方法并使用其返回值。返回值为未知类型,但总是Object。
-
通配符使用举例
public static void main(String[] args) { List<?> list = null; list = new ArrayList<String>(); list = new ArrayList<Double>(); //list.add(3);//编译不通过,不能添加null以外的元素 list.add(null); List<String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); l1.add("123"); l2.add(15); read(l1); read(l2); } public static void read(List<?> list) { for(Object o : list) { System.out.println(o); } }
-
SomeTips
- 方法声明时,返回值类型前<>里不能使用?
- 泛型类的声明时,不能使用?
- 创建对象时,不能使用?
-
有限制条件的通配符的使用
- <?> 允许所有泛型的引用调用
-
通配符指定上限:
上线 extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即 <=
-
通配符指定下限:
下限super:使用时指定的类型不能小于操作的类,即>=
-
举例:
- <? extends Number>
只允许泛型为 Number 及 Number 子类的引用调用
- <? super Number>
只允许泛型为 Number 及 Number 父类的引用调用
- <? extends Comparable>
只允许泛型为实现 Comparable 接口的实现类的引用调用
- <? extends Number>