重点

当在声明类或接口时,类或接口中定义某个成员时,该成员有些类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么可以使用泛型

12.2.1 声明泛型类

语法格式

  1. 【修饰符】 class 类名<类型变量列表> extends 父类】 implements 父接口们】{
  2. }
  3. public class NewClass<T> implements List<T>{}

注意

  • <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示,例如:<T><K,V>
  • <类型变量列表>中的类型变量不能用于静态成员上

什么时候使用泛型类或泛型接口呢?

  • 当某个类的非静态实例变量的类型不确定,需要在创建对象或子类继承时才能确定
  • 当某个(些)类的非静态方法的形参类型不确定,需要在创建对象或子类继承时才能确定

12.2.2 泛型类注意事项

  • 泛型类,如果没有指定具体的数据类型,此时,类型变量是 <Object>
  • 泛型的类型变量只能是引用类型(Integer、Double),不能是基本数据类型(int、double)
  • 泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是同一个类

12.2.3 泛型类 🌰

需求背景

例如:要声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定

  • 语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”
  • 数学老师希望成绩是89.5, 65.0
  • 英语老师希望成绩是’A’,’B’,’C’,’D’,’E’
  • 那么在设计这个学生类时,就可以使用泛型

声明泛型类

public class Student<T>{
    private String name;
    private T score;

    public Student() {
        super();
    }
    public Student(String name, T score) {
        super();
        this.name = name;
        this.score = score;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public T getScore() {
        return score;
    }
    public void setScore(T score) {
        this.score = score;
    }
    @Override
    public String toString() {
        return "姓名:" + name + ", 成绩:" + score;
    }
}

使用泛型类

public class TestGeneric{
    public static void main(String[] args) {
        //语文老师使用时:
        Student<String> stu1 = new Student<String>("张三", "良好");

        //数学老师使用时:
        //Student<double> stu2 = new Student<double>("张三", 90.5);//错误,必须是引用数据类型
        Student<Double> stu2 = new Student<Double>("张三", 90.5);

        //英语老师使用时:
        Student<Character> stu3 = new Student<Character>("张三", 'C');

        //错误的指定
        //Student<Object> stu = new Student<String>();//错误的 因为类型不一样
    }
}

验证不同的泛型类型,都属于同一个类

@Test
void testStudent() {
    //语文老师使用时:
    Student<String> stu1 = new Student<String>("张三", "良好");

    //数学老师使用时:
    Student<Double> stu2 = new Student<Double>("张三", 90.5);

    // 判断是否是同一个类
    System.out.println(stu1.getClass() == stu2.getClass());
}

重点

  • JDK1.7 支持简写形式:Student stu1 = new Student<>("张三", "良好");
  • 指定泛型实参时,必须左右两边一致,不存在多态现象

12.2.4 从泛型类派生子类

两种情况

  • 子类也是泛型类,子类和父类的泛型类型要一致
  • 子类不是泛型类,父类要明确泛型的数据类型

子类也是泛型类

class ChildGeneric<T> extends Generic<T>

// 错误示范
// class ChildGeneric<E> extends Generic<T>
// class ChildGeneric<T> extends Generic<E>

声明泛型子类、父类

/**
 * 泛型父类
 */
class Parent<E> {
    private E value;

    public E getValue() {
        return value;
    }

    public void setValue(E value) {
        this.value = value;
    }
}


/**
 * 泛型类派生子类,子类也是泛型类,那么子类的泛型标识要和父类一致。
 *
 * @param <T> 子类、父类都得是 <T>
 */
class ChildFirst<T> extends Parent<T> {
    @Override
    public T getValue() {
        System.out.println(super.getValue());
        return super.getValue();
    }
}

使用泛型子类

@Test
void testFirst() {
    ChildFirst<String> child = new ChildFirst<>();
    child.setValue("字符串啊");
    child.getValue();
}

子类不是泛型类

class ChildGeneric extends Generic<String>

// 错误示范
// class ChildGeneric extends Generic<T>

声明泛型子类

还是上面的父类

/**
 * 泛型类派生子类,如果子类不是泛型类,那么父类要明确数据类型
 */
class ChildSecond extends Parent<Integer> {
    @Override
    public Integer getValue() {
        System.out.println(super.getValue());
        return super.getValue();
    }

    @Override
    public void setValue(Integer value) {
        super.setValue(value);
    }
}

使用泛型子类

@Test
void testSecond() {
    ChildSecond child = new ChildSecond();
    child.setValue(112233);
    child.getValue();
}

无需添加泛型实参

12.2.5 练习

  1. 声明员工类型 Employee,包含姓名(String),薪资(double),年龄(int)
  2. 员工类 Employee 实现 java.lang.Comparable 接口,指定 T 为 Employee 类型,重写抽象方法,按照薪资比较大小,薪资相同的按照姓名的自然顺序比较大小
  3. 在测试类中创建 Employee 数组,然后调用 Arrays.sort(Object[] arr) 方法进行排序,遍历显示员工信息
  4. 再次调用 Arrays.sort(Object[] arr,Comparator c) 方法进行按照年龄排序,年龄相同的安装姓名自然顺序比较大小,遍历显示员工信息 ```java package basic_012;

import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test;

import java.util.Arrays;

@NoArgsConstructor @AllArgsConstructor @Data class Employee implements Comparable { private String name; private double salary; private int age;

// 重写抽象方法,按照薪资比较大小,薪资相同的按照姓名的自然顺序比较大小。
@Override
public int compareTo(@NotNull Employee o) {
    if (this.getSalary() == o.getSalary()) {
        // name是String类型,有compareTo方法
        return this.getName().compareTo(o.getName());
    }
    return Double.compare(this.getSalary(), o.getSalary());
}

}

public class TPractice {

@Test
void test() {
    Employee[] employees = new Employee[3];
    employees[0] = new Employee("Irene", 18000, 18);
    employees[1] = new Employee("Jack", 14000, 28);
    employees[2] = new Employee("Alice", 14000, 24);

    Arrays.sort(employees);

    for (Employee employee : employees) {
        System.out.println(employee);
    }
}

@Test
void test2() {
    Employee[] employees = new Employee[3];
    employees[0] = new Employee("Irene", 18000, 18);
    employees[1] = new Employee("Jack", 14000, 28);
    employees[2] = new Employee("Alice", 14000, 24);

    Arrays.sort(employees, (o1, o2) -> {
        // 按照年龄排序,年龄相同的安装姓名自然顺序比较大小
        if (o1.getAge() == o2.getAge()) {
            return o1.getName().compareTo(o2.getName());
        }
        return Double.compare(o1.getAge(), o2.getAge());
    });

    for (Employee employee : employees) {
        System.out.println(employee);
    }
}

} ```