12.1.1 泛型的引入

例如:生产瓶子的厂家,一开始并不知道将来会用瓶子装什么,什么都可以装,但是有的时候,在使用时,想要限定某个瓶子只能用来装什么,这样不会装错,而用的时候也可以放心的使用,无需再三思量;生活中是在使用这个瓶子时在瓶子上“贴标签”,这样就轻松解决了问题
1563412556491.png
还有,在 Java 中在声明方法时,当在完成方法功能时如果有未知的数据需要参与,这些未知的数据需要在调用方法时才能确定,那么把这样的数据通过形参表示
那么在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入值就可以了
1563414312500.png

  • 受以上两点启发,JDK1.5 设计了泛型的概念泛型即为“类型参数”,这个类型参数在声明它的类、接口或方法中,代表未知的通用的类型
  • 例如:Java.lang.Comparable 接口和 Java.util.Comparator 接口,是用于对象比较大小的规范接口,这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0
  • 但是并不确定是什么类型的对象比较大小,之前的时候只能用 Object 类型表示,使用时既麻烦又不安全,因此 JDK1.5 就给它们增加了泛型 ```java public interface Comparable{ int compareTo(T o) ; }

public interface Comparator{ int compare(T o1, T o2) ; }

  1. 其中就是类型参数,即泛型
  2. <a name="7a66e2b5"></a>
  3. ### 12.1.2 泛型的好处
  4. <a name="ZjfX6"></a>
  5. #### Java Bean
  6. 圆类型
  7. ```java
  8. class Circle{
  9. private double radius;
  10. public Circle(double radius) {
  11. super();
  12. this.radius = radius;
  13. }
  14. public double getRadius() {
  15. return radius;
  16. }
  17. public void setRadius(double radius) {
  18. this.radius = radius;
  19. }
  20. @Override
  21. public String toString() {
  22. return "Circle [radius=" + radius + "]";
  23. }
  24. }

比较器

import java.util.Comparator;

public class CircleComparator implements Comparator{

    @Override
    public int compare(Object o1, Object o2) {
        //强制类型转换
        Circle c1 = (Circle) o1;
        Circle c2 = (Circle) o2;
        return Double.compare(c1.getRadius(), c2.getRadius());
    }

}

测试类

public class TestGeneric {
    public static void main(String[] args) {
        CircleComparator com = new CircleComparator();
        System.out.println(com.compare(new Circle(1), new Circle(2)));

        System.out.println(com.compare("圆1", "圆2"));//运行时异常:ClassCastException
    }
}
  • 那么在使用如上面这样的接口时,如果没有泛型或不指定泛型,很麻烦,而且有安全隐患
  • 因为在设计(编译)Comparator 接口时,不知道它会用于哪种类型的对象比较,因此只能将 compare 方法的形参设计为 Object 类型,而实际在 compare 方法中需要向下转型为 Circle,才能调用 Circle 类的 getRadius() 获取半径值进行比较

使用泛型

比较器

class CircleComparator implements Comparator<Circle>{

    @Override
    public int compare(Circle o1, Circle o2) {
        //不再需要强制类型转换,代码更简洁
        return Double.compare(o1.getRadius(), o2.getRadius());
    }

}

测试类

import java.util.Comparator;

public class TestGeneric {
    public static void main(String[] args) {
        CircleComparator com = new CircleComparator();
        System.out.println(com.compare(new Circle(1), new Circle(2)));

//        System.out.println(com.compare("圆1", "圆2"));//编译错误,因为"圆1", "圆2"不是Circle类型,是String类型,编译器提前报错,而不是冒着风险在运行时再报错
    }
}
  • 如果使用泛型,那么既能保证安全,又能简化代码
  • 因为把不安全的因素在编译期间就排除了
  • 既然通过了编译,那么类型一定是符合要求的,就避免了类型转换

12.1.3 泛型的相关名词

  • <类型>这种语法形式就叫泛型
  • <T>是类型变量(Type Variables),而<T>是代表未知的数据类型,可以指定为<String>``<Integer>``<Circle>等,那么<类型>的形式称为类型参数;
  • 类比方法的参数的概念,可以把<T>称为类型形参,<Circle>称为类型实参

自从有了泛型之后, Java 的数据类型就更丰富了

image-20200521081637509.png
Class:Class 类的实例表示正在运行的 Java 应用程序中的类和接口

  • 枚举是一种类,注释是一种接口
  • 每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象
  • 基本的 Java 类型(booleanbytecharshortintlongfloatdouble)和关键字 void 也表示为 Class 对象

GenericArrayType:泛化的数组类型,即T[]
ParameterizedType:参数化类型,例如:Comparator,Comparator
TypeVariable:类型变量,例如:Comparator 中的 T,Map 中的 K,V
WildcardType:通配符类型,例如:Comparator<?> 等

12.1.4 在哪里可以声明类型变量

声明类或接口时,在类名或接口名后面声明类型变量,把这样的类或接口称为泛型类或泛型接口

【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 父接口们】{

}
【修饰符】 interface 接口名<类型变量列表> 【implements 父接口们】{

}

例如:
public class ArrayList<E>    
public interface Map<K,V>{
    ....
}

声明方法时,在【修饰符】与返回值类型之间声明类型变量,把声明(是声明不是单纯的使用)了类型变量的方法称为泛型方法

【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
    //...
}

例如:java.util.Arrays 类中的
public static <T> List<T> asList(T... a){
    ....
}

12.1.5 <类型>类型常见的一些写法

看一些源代码能经常看到一些不同泛型 <类型>的写法,举几个 🌰

  • E - Element(在集合中使用,因为集合中存放的是元素)
  • T - Type(表示 Java 类,包括基本的类和自定义的类)
  • K - Key(表示键,比如 Map 中的 key)
  • V - Value(表示值)
  • N - Number(表示数值类型)
  • ? - (表示不确定的 Java 类型)
  • S、U、V - 2nd、3rd、4th types

12.1.6 总结使用泛型的优势

  • 类型安全,如果存储数据时类型不正确,会编译错误,若不使用泛型,很可能发生运行时错误
  • 消除了强制类型的转换,使代码看起来更优雅