一、为什么要有泛型(Generic)

泛型:标签
举例:中药店,每个抽屉外面贴着标签
超市购物架上很多瓶子,每个瓶子装的是什么,有标签
泛型的设计背景:集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。

1、泛型的概念

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。
从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明 该List只能保存字符串类型的对象。
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

那么为什么要有泛型呢,直接Object不是也可以存储数据吗?
1、解决元素存储的安全性问题,好比商品、药品标签,不会弄错。
2、解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。

2、在集合中没有泛型时

image.png

  1. ArrayList list = new ArrayList();
  2. // 需求:存放学生的成绩
  3. list.add(78);
  4. list.add(76);
  5. list.add(89);
  6. list.add(88);
  7. // 问题一:类型不安全
  8. // list.add("Tom");
  9. for(Object score : list){
  10. //问题二:强转时,可能出现ClassCastException
  11. int stuScore = (Integer) score;
  12. System.out.println(stuScore);
  13. }

3、在集合中有泛型时

image.png

  1. ArrayList<Integer> list = new ArrayList<Integer>();
  2. list.add(78);
  3. list.add(87);
  4. list.add(99);
  5. list.add(65);
  6. // 编译时,就会进行类型检查,保证数据的安全
  7. // list.add("Tom");
  8. // 方式一:
  9. //for(Integer score : list){
  10. // // 避免了强转操作
  11. // int stuScore = score;
  12. // System.out.println(stuScore);
  13. //}
  14. // 方式二:
  15. Iterator<Integer> iterator = list.iterator();
  16. while(iterator.hasNext()){
  17. int stuScore = iterator.next();
  18. System.out.println(stuScore);
  19. }

Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。

二、在集合中使用泛型

  1. ArrayList<Integer> list = new ArrayList<>();//类型推断
  2. list.add(78);
  3. list.add(88);
  4. list.add(77);
  5. list.add(66);
  6. // 遍历方式一:
  7. //for(Integer i : list){
  8. // 不需要强转
  9. //System.out.println(i);
  10. //}
  11. // 遍历方式二:
  12. Iterator<Integer> iterator = list.iterator();
  13. while(iterator.hasNext()){
  14. System.out.println(iterator.next());
  15. }
  1. // Map<String,Integer> map = new HashMap<String,Integer>();
  2. // jdk7新特性:类型推断
  3. Map<String,Integer> map = new HashMap<>();
  4. map.put("Tom",87);
  5. map.put("Jerry",87);
  6. map.put("Jack",67);
  7. // map.put(123,"ABC");
  8. // 泛型的嵌套
  9. Set<Map.Entry<String,Integer>> entry = map.entrySet();
  10. Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
  11. while(iterator.hasNext()){
  12. Map.Entry<String, Integer> e = iterator.next();
  13. String key = e.getKey();
  14. Integer value = e.getValue();
  15. System.out.println(key + "----" + value);
  16. }

总结:
① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型
③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
比如:add(E e) —->实例化以后:add(Integer e)
④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型

三、自定义泛型结构

1、泛型声明与实例化

1.1 泛型的声明

interface List 和 class GenTest
其中,T, K, V不代表值,而是表示类型。这里使用任意字母都可以。常用T表示,是Type的缩写。

1.2 泛型的实例化

一定要在类名后面指定类型参数的值(类型)。
如:List strList = new ArrayList();
Iterator iterator = customers.iterator();

  • T只能是类,不能用基本数据类型填充。但可以使用包装类填充
  • 把一个集合中的内容限制为一个特定的数据类型,这就是generics背后的核心思想 ```java // JDK 1.5 之前 Comparable c = new Date(); System.out.println(c.compareTo(“red”));

// JDK 1.5 Comparable c = new Date(); System.out.println(c.compareTo(“red”));

  1. 体会:使用泛型的主要优点是能够在编译时而不是在运行时检测错误。
  2. <a name="SEIuZ"></a>
  3. ## 2、自定义泛型类、泛型接口
  4. ```java
  5. // 自定义泛型类
  6. public class Order<T> {
  7. String orderName;
  8. int orderId;
  9. // 类的内部结构就可以使用类的泛型
  10. T orderT;
  11. public Order(){
  12. // 编译不通过
  13. // T[] arr = new T[10];
  14. // 编译通过
  15. T[] arr = (T[]) new Object[10];
  16. }
  17. public Order(String orderName,int orderId,T orderT){
  18. this.orderName = orderName;
  19. this.orderId = orderId;
  20. this.orderT = orderT;
  21. }
  22. // 如下的三个方法都不是泛型方法
  23. public T getOrderT(){
  24. return orderT;
  25. }
  26. public void setOrderT(T orderT){
  27. this.orderT = orderT;
  28. }
  29. // 静态方法中不能使用类的泛型。
  30. // public static void show(T orderT){
  31. // System.out.println(orderT);
  32. // }
  33. public void show(){
  34. //编译不通过
  35. // try{
  36. //
  37. // }catch(T t){
  38. //
  39. // }
  40. }
  41. // 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
  42. // 换句话说,泛型方法所属的类是不是泛型类都没有关系。
  43. // 泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
  44. public static <E> List<E> copyFromArrayToList(E[] arr){
  45. ArrayList<E> list = new ArrayList<>();
  46. for(E e : arr){
  47. list.add(e);
  48. }
  49. return list;
  50. }
  51. }

1、泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
2、泛型类的构造器如下:public GenericClass(){}。
而下面是错误的:public GenericClass(){}
3、实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
4、泛型不同的引用不能相互赋值。

尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。

5、泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用
6、如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
7、jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
8、泛型的指定中不能使用基本数据类型,可以使用包装类替换。
9、在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型
10、异常类不能是泛型的
11、不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
12、父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

  • 子类不保留父类的泛型:按需实现
    • 没有类型:擦除
    • 具体类型
  • 子类保留父类的泛型:泛型子类
    • 全部保留
    • 部分保留

结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型

  1. class Father<T1, T2> {
  2. }
  3. // 子类不保留父类的泛型
  4. // 1) 没有类型 擦除
  5. class Son1 extends Father {// 等价于class Son extends Father<Object, Object>{
  6. }
  7. // 2) 具体类型
  8. class Son2 extends Father<Integer, String> {
  9. }
  10. // 子类保留父类的泛型
  11. // 1) 全部保留
  12. class Son3<T1, T2> extends Father<T1, T2> {
  13. }
  14. // 2) 部分保留
  15. class Son4<T2> extends Father<Integer, T2> {
  16. }
  1. class Father<T1, T2> {
  2. }
  3. // 子类不保留父类的泛型
  4. // 1) 没有类型 擦除
  5. class Son<A, B> extends Father{ // 等价于class Son extends Father<Object,Object>{
  6. }
  7. // 2) 具体类型
  8. class Son2<A, B> extends Father<Integer, String> {
  9. }
  10. // 子类保留父类的泛型
  11. // 1) 全部保留
  12. class Son3<T1, T2, A, B> extends Father<T1, T2> {
  13. }
  14. // 2) 部分保留
  15. class Son4<T2, A, B> extends Father<Integer, T2> {
  16. }
  1. // 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型
  2. // 要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
  3. Order order = new Order();
  4. order.setOrderT(123);
  5. order.setOrderT("ABC");
  6. //建议:实例化时指明类的泛型
  7. Order<String> order1 = new Order<String>("orderAA", 1001, "order:AA");
  8. order1.setOrderT("AA:hello");
  1. // SubOrder:不是泛型类
  2. public class SubOrder extends Order<Integer> {}
  3. SubOrder sub1 = new SubOrder();
  4. // 由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
  5. sub1.setOrderT(1122);
  6. // SubOrder1<T>:仍然是泛型类
  7. public class SubOrder1<T> extends Order<T> {}
  8. SubOrder1<String> sub2 = new SubOrder1<>();
  9. sub2.setOrderT("order2...");

3、自定义泛型方法

1、方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
2、泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
3、泛型方法声明泛型时也可以指定上限(在第五节讲)

  1. public class DAO {
  2. public <E> E get(int id, E e) {
  3. E result = null;
  4. return result;
  5. }
  6. }
  1. public static void main(String[] args) {
  2. Object[] ao = new Object[100];
  3. Collection<Object> co = new ArrayList<Object>();
  4. fromArrayToCollection(ao, co);
  5. String[] sa = new String[20];
  6. Collection<String> cs = new ArrayList<>();
  7. fromArrayToCollection(sa, cs);
  8. Collection<Double> cd = new ArrayList<>();
  9. // 下面代码中T是Double类,但sa是String类型,编译错误。
  10. // fromArrayToCollection(sa, cd);
  11. // 下面代码中T是Object类型,sa是String类型,可以赋值成功。
  12. fromArrayToCollection(sa, co);
  13. }
  14. public static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
  15. for (T o : a) {
  16. c.add(o);
  17. }
  18. }
  1. class Creature{}
  2. class Person extends Creature{}
  3. class Man extends Person{}
  4. class PersonTest {
  5. public static <T extends Person> void test(T t){
  6. System.out.println(t);
  7. }
  8. public static void main(String[] args) {
  9. test(new Person());
  10. test(new Man());
  11. //The method test(T) in the type PersonTest is not
  12. //applicable for the arguments (Creature)
  13. test(new Creature());
  14. }
  15. }

四、泛型在继承上的体现

  1. // 请输出如下来两段代码有何不同
  2. public void printCollection(Collection c) {
  3. Iterator i = c.iterator();
  4. for (int k = 0; k < c.size(); k++) {
  5. System.out.println(i.next());
  6. }
  7. }
  8. public void printCollection(Collection<Object> c) {
  9. for (Object e : c) {
  10. System.out.println(e);
  11. }
  12. }

如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G并不是G的子类型!
比如:String是Object的子类,但是List并不是List的子类。
补充:类A是类B的父类,A 是 B 的父类
image.png

  1. public void testGenericAndSubClass() {
  2. Person[] persons = null;
  3. Man[] mans = null;
  4. // 而 Person[] 是 Man[] 的父类. persons = mans;
  5. Person p = mans[0];
  6. // 在泛型的集合上
  7. List<Person> personList = null;
  8. List<Man> manList = null;
  9. // personList = manList;(报错)
  10. }
  1. Object obj = null;
  2. String str = null;
  3. obj = str;
  4. Object[] arr1 = null;
  5. String[] arr2 = null;
  6. arr1 = arr2;
  7. // 编译不通过
  8. // Date date = new Date();
  9. // str = date;
  10. List<Object> list1 = null;
  11. List<String> list2 = new ArrayList<String>();
  12. // 此时的list1和list2的类型不具有子父类关系
  13. // 编译不通过
  14. // list1 = list2;

五、通配符的使用

1、使用类型通配符:?
比如:List<?> ,Map<?, ?>
List<?>是List、List等各种泛型List的父类。
2、读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
3、写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。

  • 唯一的例外是null,它是所有类型的成员。

4、将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList();
c.add(new Object()); // 编译时错误
因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。
5、唯一的例外的是null,它是所有类型的成员。
6、另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object。

  1. List<Object> list1 = null;
  2. List<String> list2 = null;
  3. List<?> list = null;
  4. list = list1;
  5. list = list2;
  6. // 编译通过
  7. print(list1);
  8. print(list2);
  9. List<String> list3 = new ArrayList<>();
  10. list3.add("AA");
  11. list3.add("BB");
  12. list3.add("CC");
  13. list = list3;
  14. // 添加(写入):对于List<?>就不能向其内部添加数据。
  15. // 除了添加null之外,编译失败。
  16. // list.add("DD");
  17. // list.add('?');
  18. list.add(null);
  19. // 获取(读取):允许读取数据,读取的数据类型为Object。
  20. Object o = list.get(0);
  21. System.out.println(o);
  1. public static void main(String[] args) {
  2. List<?> list = null;
  3. list = new ArrayList<String>();
  4. list = new ArrayList<Double>();
  5. // list.add(3);//编译不通过
  6. list.add(null);
  7. List<String> l1 = new ArrayList<String>();
  8. List<Integer> l2 = new ArrayList<Integer>();
  9. l1.add("尚硅谷");
  10. l2.add(15);
  11. read(l1);
  12. read(l2);
  13. }
  14. public static void read(List<?> list) {
  15. for (Object o : list) {
  16. System.out.println(o);
  17. }
  18. }
  1. // 注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
  2. public static <?> void test(ArrayList<?> list){
  3. }
  4. // 注意点2:编译错误:不能用在泛型类的声明上
  5. class GenericTypeClass<?>{
  6. }
  7. // 注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
  8. ArrayList<?> list2 = new ArrayList<?>();

1、有限制的通配符

  • <?>:允许所有泛型的引用调用
  • 通配符指定上限
    • 上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
  • 通配符指定下限
    • 下限super:使用时指定的类型不能小于操作的类,即>=

举例:

  • <? extends Number> (无穷小 , Number]
    • 只允许泛型为Number及Number子类的引用调用
  • <? super Number> [Number , 无穷大)
    • 只允许泛型为Number及Number父类的引用调用
  • <? extends Comparable>
    • 只允许泛型为实现Comparable接口的实现类的引用调用 ```java List<? extends Person> list1 = null; List<? super Person> list2 = null;

List list3 = new ArrayList(); List list4 = new ArrayList(); List list5 = new ArrayList();

list1 = list3; list1 = list4; // 编译失败 // list1 = list5;

// 编译失败 // list2 = list3; list2 = list4; list2 = list5;

// 读取数据: list1 = list3; Person p = list1.get(0); // 编译不通过 // Student s = list1.get(0);

list2 = list4; Object obj = list2.get(0); // 编译不通过 // Person obj = list2.get(0);

// 写入数据: // 编译不通过 // list1.add(new Student());

// 编译通过 list2.add(new Person()); list2.add(new Student());

  1. ```java
  2. public static void printCollection3(Collection<? extends Person> coll) {
  3. //Iterator只能用Iterator<?>或Iterator<? extends Person>.why? Iterator<?> iterator = coll.iterator();
  4. while (iterator.hasNext()) {
  5. System.out.println(iterator.next());
  6. }
  7. }
  8. public static void printCollection4(Collection<? super Person> coll) {
  9. //Iterator只能用Iterator<?>或Iterator<? super Person>.why?
  10. Iterator<?> iterator = coll.iterator();
  11. while (iterator.hasNext()) {
  12. System.out.println(iterator.next());
  13. }
  14. }

六、泛型应用举例

  1. public static void main(String[] args) {
  2. HashMap<String, ArrayList<Citizen>> map = new HashMap<String, ArrayList<Citizen>>();
  3. ArrayList<Citizen> list = new ArrayList<Citizen>();
  4. list.add(new Citizen("刘恺威"));
  5. list.add(new Citizen("杨幂"));
  6. list.add(new Citizen("小糯米"));
  7. map.put("刘恺威", list);
  8. Set<Entry<String, ArrayList<Citizen>>> entrySet = map.entrySet();
  9. Iterator<Entry<String, ArrayList<Citizen>>> iterator = entrySet.iterator();
  10. while (iterator.hasNext()) {
  11. Entry<String, ArrayList<Citizen>> entry = iterator.next();
  12. String key = entry.getKey();
  13. ArrayList<Citizen> value = entry.getValue();
  14. System.out.println(" 户 主 :" + key);
  15. System.out.println("家庭成员:" + value);
  16. }
  17. }

12-1 为什么要有泛型