第一章:泛型的概述

1.1 泛型设计的背景

  • 集合容器类在设计阶段/声明阶段不能确定这个容器到底存的是什么类型的对象,所以在 JDK 5 之前只能将元素类型设计为 Object ,JDK 5 之后使用泛型来解决。
  • 因为这个时候除了元素的类型不确定,其他的部分是确定的,例如:关于这个元素如何保存,如何管理等是确定的,因此,此时可以将元素的类型设置成一个参数,这个类型参数叫做泛型。比如:Collection<E>List<E> 中的 <E> 就是类型参数,即泛型。

1.2 泛型的概念

  • 所谓泛型,就是运行在定义类、接口或方法时通过一个标识表示类中某个属性的类型或者某个方法的返回值以及参数类型。这个参数将在使用的时候确定。
  • 从 JDK 5 以后,Java 引入了参数化类型( Parameterized type )的概念,允许我们在创建集合的时候指定集合元素的类型,如:List<String> ,这表明该 List 只能保存 String 类型的元素。
  • JDK 5 改写了集合框架中的全部接口和类,为这些接口和类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
  • 语法:
  1. <类型>

指定一种类型的格式,尖括号里面可以任意书写,按照变量的定义规则即可,一般只写一个字母。 比如:<E><T> 等。

  1. <类型1,类型2,……>

指定多种类型的格式,多种类型之间用逗号隔开。比如:<K,V> 等。

1.3 为什么要有泛型?

  • 泛型解决了如下的问题:
  • ① 解决元素存储的安全性问题。
  • ② 解决获取数据元素时,需要类型强制转换的问题。

集合中没有泛型的时候.png

集合中有泛型的时候.png

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

1.4 泛型的相关名词

  • 自从有了泛型以后,Java 的数据类型就更加丰富了。

泛型的相关名词.png

  • Class :Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注解是一种接口。每个数组被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的Java数据类型(byte 、short 、int 、long 、double 、float 、char )和关键字 void 也表示为 Class 对象。
  • GenericArrayType :泛化的数组类型,即 T[]
  • ParameterizedType :参数化类型,例如:Comparator<String>
  • TypeVariable :类型变量,例如:Comparator<T> 中的 T 。
  • WildcardType :通配符类型,例如:Comparator<?> 等。

第二章:自定义泛型结构

2.1 概述

  • 泛型可以声明在类、接口和方法上。

2.2 泛型类和泛型接口

  • 泛型类语法:
  1. 【修饰符】 class 类名<类型变量列表> extends 父类】 implements 父接口们】{
  2. ...
  3. }
  • 泛型接口语法:
  1. 【修饰符】 interface 接口名<类型变量列表> implements 父接口们】{
  2. ...
  3. }

注意:

  • <类型变量列表> :可以是一个或多个类型变量,一般都使用单个的大写字母表示,如:<T><E> 等。
  • <类型变量列表> 中的类型变量不能作用于静态成员上。
  • 什么时候使用泛型类或泛型接口?
  • ① 当某个类的非静态实例变量的类型不确定,需要在创建对象或子类继承的时候才能确定。
  • ② 当某个类的非静态方法的形参类型不确定,需要在创建对象或子类继承的时候才能确定。
  • 示例:我们要声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢,因为,语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”,数学老师希望成绩是 89.5 , 65.0,英语老师希望成绩是 ‘A’ , ‘B’ , ‘C’ , ‘D’ , ‘E’ 。那么我们在设计这个学生类时,就可以使用泛型。
  1. package com.github.generic.demo1;
  2. /**
  3. * @author 许大仙
  4. * @version 1.0
  5. * @since 2021-09-30 14:25
  6. */
  7. public class Student<T> {
  8. private String name;
  9. private T score;
  10. public Student() {}
  11. public Student(String name, T score) {
  12. this.name = name;
  13. this.score = score;
  14. }
  15. public String getName() {
  16. return this.name;
  17. }
  18. public void setName(String name) {
  19. this.name = name;
  20. }
  21. public T getScore() {
  22. return this.score;
  23. }
  24. public void setScore(T score) {
  25. this.score = score;
  26. }
  27. @Override
  28. public String toString() {
  29. return "Student{" + "name='" + this.name + '\'' + ", score=" + this.score + '}';
  30. }
  31. }

2.3 使用泛型类和泛型接口

  • 在使用泛型类和泛型接口的时候,我们需要指定泛型变量的实际类型参数:
  • ① 实际类型参数必须是引用数据类型,不能是基本数据类型。
  • ② 在创建类的对象时指定类型变量对应的实际类型参数。

  • 示例:

  1. package com.github.generic.demo1;
  2. /**
  3. * @author 许大仙
  4. * @version 1.0
  5. * @since 2021-09-30 14:26
  6. */
  7. public class Test {
  8. public static void main(String[] args) {
  9. // 语文老师
  10. Student<String> stu1 = new Student<>("张三", "良好");
  11. System.out.println("stu1 = " + stu1); // stu1 = Student{name='张三', score=良好}
  12. // 数学老师
  13. Student<Double> stu2 = new Student<>("李四", 80.0);
  14. System.out.println("stu2 = " + stu2); // stu2 = Student{name='李四', score=80.0}
  15. // 英语老师
  16. Student<Character> stu3 = new Student<>("王五", 'A');
  17. System.out.println("stu3 = " + stu3); // stu3 = Student{name='王五', score=A}
  18. }
  19. }

2.4 类型变量的上限

  • 当在声明类型变量的时候,如果不希望这个类型变量代表任意的数据类型,而是某个系列的引用数据类型,那么可以设定类型变量的上限。
  • 语法:
  1. <类型变量 extends 上限>
  1. <类型变量 extends 上限1 & 上限2>

注意:

  • ① 如果多个上限中有类和接口,那么只能有一个类,并且必须写在左边。
  • ② 如果多个上限中都是接口,且有多个,没有顺序要求。
  • ③ 如果在声明 <类型变量> 时没有指定任何上限,则默认上下是 java.lang.Object 。
  • 示例:声明一个两个数求和的工具类,要求两个加数必须是 Number 数字类型,并且实现 Comparable 接口。
  1. package com.github.generic.demo2;
  2. import java.math.BigDecimal;
  3. import java.math.BigInteger;
  4. /**
  5. * @author 许大仙
  6. * @version 1.0
  7. * @since 2021-09-30 14:59
  8. */
  9. public class SumTools<T extends Number & Comparable<T>> {
  10. private T t1;
  11. private T t2;
  12. public SumTools(T t1, T t2) {
  13. this.t1 = t1;
  14. this.t2 = t2;
  15. }
  16. public T getSum() {
  17. if (this.t1 instanceof BigInteger) {
  18. return (T)((BigInteger)this.t1).add((BigInteger)this.t2);
  19. } else if (this.t1 instanceof BigDecimal) {
  20. return (T)((BigDecimal)this.t1).add((BigDecimal)this.t2);
  21. } else if (this.t1 instanceof Integer) {
  22. return (T)(Integer.valueOf((Integer)this.t1 + (Integer)this.t2));
  23. } else if (this.t1 instanceof Long) {
  24. return (T)(Long.valueOf((Long)this.t1 + (Long)this.t2));
  25. } else if (this.t1 instanceof Float) {
  26. return (T)(Float.valueOf((Float)this.t1 + (Float)this.t2));
  27. } else if (this.t1 instanceof Double) {
  28. return (T)(Double.valueOf((Double)this.t1 + (Double)this.t2));
  29. }
  30. throw new UnsupportedOperationException("不支持该操作");
  31. }
  32. }
package com.github.generic.demo2;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-30 15:02
 */
public class Test {
    public static void main(String[] args) {
        SumTools<Integer> sumTools = new SumTools<>(1, 2);
        Integer sum = sumTools.getSum();
        System.out.println("sum = " + sum);
    }
}

2.5 泛型擦除

  • 当使用参数化类型的类或接口时,如果没有指定泛型,会发生泛型擦除,自动按照最左边的第一个上限处理。如果没有指定上限,上限即为 Object 。

2.6 泛型方法

  • 在定义类、接口时可以声明 <类型变量> ,在该类的方法和属性定义、接口的方法定义中,这些 <类型变量> 可被当成普通类型来用。但是,在另外一些情况下,
    • ① 如果我们定义类、接口时没有使用 <类型变量> ,但是某个方法形参类型不确定时,可以单独这个方法定义 <类型变量> ;
    • ② 另外我们之前说类和接口上的类型形参是不能用于静态方法中,那么当某个静态方法的形参类型不确定时,可以单独定义 <类型变量> 。
  • JDK1.5 之后,还提供了泛型方法的支持。
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
    //...
}
  • 示例:
package com.github.generic.demo3;

public class MyArrays {

    /**
     * 排序
     * 
     * @param arr
     * @param <T>
     */
    public static <T extends Comparable<T>> void sort(T[] arr) {
        for (int i = 1; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                if (arr[j].compareTo(arr[j + 1]) > 0) {
                    T temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
}

第三章:类型通配符

3.1 <?> 任意类型

  • <?> 表示元素类型可以是任意类型的。
  • 注意事项:

    • 读取 List<?> 的对象 list 中的元素时,永远是安全的,因为不管 list 的真实类型是什么,它包含的都是 Object 。
    • 写入 List<?> 中的元素时,不行。因为我们不知道 List 的元素类型,我们不能向其中添加对象。唯一的例外是 null ,它是所有类型的成员。
    • <?> 不能用来泛型方法声明上。

      // 编译错误
      public static <?> void test(ArrayList<?> list){
      }
      
    • <?> 不能用来泛型类的声明上。

      // 编译错误
      public GenericTypeClass<?>{
      }
      
    • <?> 不能用在创建对象上,右边属于创建集合对象。

      // 编译错误
      ArrayList<?> list2 = new ArrayList<?>();
      
  • 示例:

package com.github.generic.demo4;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-30 16:09
 */
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("aa");
        list.add("cc");
        list.add("bb");

        read(list);
    }

    public static void read(List<?> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }
}

3.2 <? extends 类型> 类型通配符上限

  • 示例:
package com.github.generic.demo5;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-30 14:25
 */
public class Student<T> {
    private String name;

    private T score;

    public Student() {}

    public Student(String name, T score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public T getScore() {
        return this.score;
    }

    public void setScore(T score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" + "name='" + this.name + '\'' + ", score=" + this.score + '}';
    }
}
package com.github.generic.demo5;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-30 16:17
 */
public class StudentService {
    public static Student<? extends Comparable> max(Student<? extends Comparable>[] arr) {
        Student<? extends Comparable> max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if (arr[i].getScore().compareTo(max.getScore()) > 0) {
                max = arr[i];
            }
        }
        return max;
    }
}
package com.github.generic.demo5;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-30 16:17
 */
public class Test {
    public static void main(String[] args) {
        Student<? extends Double>[] arr = new Student[3];
        arr[0] = new Student<>("张三", 90.5);
        arr[1] = new Student<>("李四", 80.5);
        arr[2] = new Student<>("王五", 94.5);

        Student<? extends Comparable> max = StudentService.max(arr);
        System.out.println(max);
    }
}

3.3 <? super类型> 类型通配符下限

  • 示例:
package com.github.generic.demo6;

import java.util.Comparator;

public class MyArrays{
   public static <T> void sort(T[] arr, Comparator<? super T> c){
      for (int i = 1; i < arr.length; i++) {
         for (int j = 0; j < arr.length-i; j++) {
            if(c.compare(arr[j], arr[j+1])>0){
               T temp = arr[j];
               arr[j] = arr[j+1];
               arr[j+1] = temp;
            }
         }
      }
   }
}