Java基础学习——第十二章 泛型

Java基础学习——第十二章 泛型

一、泛型的概念

1. 泛型的设计背景

  • 集合容器类在声明阶段无法确定实际存储的是什么类型的对象,故JDK5.0之前只能把元素类型声明为Object
  • JDK1.5之后使用泛型来解决:由于此时除了元素的类型不确定之外,其他的部分都是确定的, 例如关于这个元素如何保存, 如何管理等。因此把元素的类型设计成一个参数, 这种参数化类型的概念就是泛型
    • Collection, List, ArrayList 中的就是泛型参数

2. 泛型的概念

  • 泛型:在定义类、接口时,通过一个标识表示类中某个属性的类型、某个方法的返回值或形参类型、某个构造器的形参类型。 这个泛型参数将在使用时(例如:继承这个类或实现这个接口; 用这个类型声明变量、 创建对象时)确定(即传入实际的泛型参数, 也称为类型实参

3. 为什么要有泛型

  1. 解决集合中元素类型不安全的问题:
    • 集合中未使用泛型:任何类型的对象都可以添加到集合中(类型不安全的)
    • 集合中使用泛型后:只有指定类型的对象才可以添加到集合中(类型安全的)
  2. 解决获取数据元素时,需要强制类型转换的问题(强转可能会抛ClassCastException异常
    • 集合中未使用泛型:读取出来的对象需要进行强制类型转换,还可能抛ClassCastException异常
    • 集合中使用泛型后:读取出来的对象不需要进行强制类型转换
  3. 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常
  4. 代码更加简洁、健壮

Java基础学习——第十二章 泛型_第1张图片

Java基础学习——第十二章 泛型_第2张图片

二、在集合中使用泛型

  1. JDK5.0改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,允许我们在实例化集合对象时再指定集合元素的类型
  2. 实例化集合对象时,在类名、接口名后声明一个类型实参"<指定类型>",显式指定集合元素的类型(泛型类型)
  3. JDK7.0新特性——类型推断:ArrayList list = new ArrayList<>();
  4. 指定泛型类型后,在集合类或接口内部,凡是使用到泛型的位置(如:某个属性的类型、某个方法的返回值或形参类型、某个构造器的形参类型)都会被指定为该泛型类型
    • 如:add(E e) —> add(Integer e)
  5. 泛型类型必须是一个类,不能是基本数据类型,但可以是它们的包装类
  6. 如果实例化集合对象时,没有指明泛型类型(泛型参数的具体类型),则默认为java.lang.Object类型
import org.junit.Test;
import java.util.*;

public class GenericTest {
    //集合中未使用泛型
    @Test
    public void test1() {
        ArrayList list = new ArrayList();
        //需求:存放学生的成绩
        list.add(78);
        list.add(76);
        list.add(89);
        list.add(88);
        //问题一:类型不安全:任何类型的对象都可以添加到集合中(类型不安全的)
        list.add("Tom");

        //遍历方式一:使用增强for
        for (Object score : list) {
            //问题二:强制类型转换时,有可能报ClassCastException异常
            int stuScore = (Integer) score;
            System.out.println(stuScore);
        }
    }

    //集合中使用泛型后:以ArrayList为例
    @Test
    public void test2() {
        //在实例化集合对象时,后面跟一个类型实参"<指定类型>",显式的指定集合元素的类型
        //ArrayList list = new ArrayList();
        ArrayList<Integer> list = new ArrayList<>(); //类型推断
        list.add(78);
        list.add(87);
        list.add(99);
        list.add(88);
        //解决了问题一:使用泛型,在编译时就会进行类型检查,只能往集合里添加类型实参指定的类的对象,保证元素类型的安全
        //list.add("Tom");

        //遍历方式一:使用增强for
        for (Integer score : list) {
            //解决了问题二:避免了强制类型转换
            System.out.println(score);
        }

        //遍历方式二:使用Iterator迭代器对象
        //迭代器的泛型类型实参与集合中存储的元素类型一致
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            int stuScore = iterator.next();
            System.out.println(stuScore);
        }
    }

    //集合中使用泛型后:以HashMap为例
    @Test
    public void test3() {
        //在实例化集合对象时,后面跟一个类型实参"<指定类型>",显式的指定集合元素的类型
        HashMap<String, Integer> map = new HashMap<>(); //JDK7新特性:类型推断
        map.put("Tom", 87);
        map.put("Jerry", 87);
        map.put("Jack", 67);
        //解决了问题一:使用泛型,在编译时就会进行类型检查,只能往集合里添加类型实参指定的类的对象,保证元素类型的安全
        //map.put(123, "ABC");

        //泛型的嵌套
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        //遍历方式一:使用增强for
        for (Map.Entry<String, Integer> entry : entries) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + "-->" + value);
        }

        //遍历方式二:使用Iterator迭代器对象
        //迭代器的泛型类型实参与集合中存储的元素类型一致
        Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + "-->" + value);
        }
    }
}

练习:泛型在Java比较器中的应用

  1. 自然排序:在当前要比较的对象所在类实现Comparable接口时,指明其泛型类型(当前类),此时compareTo()方法的形参类型也被指定为该泛型类型,重写compareTo()方法时无需再进行instanceof判断和强制类型转换,也不用抛异常了
  2. 定制排序:在声明Comparator接口的实现类时,指明其泛型类型(需要比较的对象所在的类),此时compare()方法的形参类型也被指定为该泛型类型,则重写compare()方法时无需再进行instanceof判断和强制类型转换,也不用抛异常了
public class EmployeeTest {
    //自然排序
    @Test
    public void test1() {
        TreeSet<Employee> set = new TreeSet<>();
        set.add(new Employee("liudehua", 55, new MyDate(1965, 5, 4)));
        set.add(new Employee("zhangxueyou", 43, new MyDate(1987, 5, 4)));
        set.add(new Employee("guofucheng", 44, new MyDate(1987, 5, 9)));
        set.add(new Employee("liming", 51, new MyDate(1954, 8, 12)));
        set.add(new Employee("liangchaowei", 21, new MyDate(1978, 12, 4)));

        //使 Employee 实现 Comparable 接口,并按 name 排序
        Iterator<Employee> iterator = set.iterator();
        while (iterator.hasNext()) {
            Employee employee = iterator.next();
            System.out.println(employee);
        }
    }

    //定制排序
    @Test
    public void test2() {
        TreeSet<Employee> set = new TreeSet<>(new Comparator<Employee>() {
            //没有指明泛型类型时的写法,默认泛型类型为Object类型
//            @Override
//            public int compare(Object o1, Object o2) {
//                if (o1 instanceof Employee && o2 instanceof Employee) {
//                    Employee e1 = (Employee) o1;
//                    Employee e2 = (Employee) o2;
//
//                    MyDate b1 = e1.getBirthday();
//                    MyDate b2 = e2.getBirthday();
//
//                    //方式一:
                    int sumYear = Integer.compare(b1.getYear(), b2.getYear());
                    if (sumYear != 0) {
                        return sumYear;
                    }
                    int sumMonth = Integer.compare(b1.getMonth(), b2.getMonth());
                    if (sumMonth != 0) {
                        return sumMonth;
                    }
                    return Integer.compare(b1.getDay(), b2.getDay());
//
//                    //方式二:
//                    return b1.compareTo(b2);
//                }
//                throw new RuntimeException("输入数据类型不一致");
//            }

            //指明泛型类型时的写法:无需instanceof判断和强制类型转换,也不用抛异常了
            @Override
            public int compare(Employee o1, Employee o2) {
                MyDate b1 = o1.getBirthday();
                MyDate b2 = o2.getBirthday();
                return b1.compareTo(b2);
            }
        });
        set.add(new Employee("liudehua", 55, new MyDate(1965, 5, 4)));
        set.add(new Employee("zhangxueyou", 43, new MyDate(1987, 5, 4)));
        set.add(new Employee("guofucheng", 44, new MyDate(1987, 5, 9)));
        set.add(new Employee("liming", 51, new MyDate(1954, 8, 12)));
        set.add(new Employee("liangchaowei", 21, new MyDate(1978, 12, 4)));

        //使 Employee 实现 Comparable 接口,并按 name 排序
        Iterator<Employee> iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}
public class Employee implements Comparable<Employee> {
    private String name;
    private int age;
    private MyDate birthday;

    public Employee() {
    }

    public Employee(String name, int age, MyDate birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }

    //没有指明泛型类型时的写法,默认泛型类型为Object类型
//    @Override
//    public int compareTo(Object o) {
//        if (o instanceof Employee) {
//            Employee e = (Employee) o;
//            return this.name.compareTo(e.name);
//        }
//        throw new RuntimeException("输入的数据类型不匹配");
//    }

    //指明泛型类型时的写法:无需instanceof判断和强制类型转换,也不用抛异常了
    @Override
    public int compareTo(Employee o) {
        return this.name.compareTo(o.name);
    }
}
public class MyDate implements Comparable<MyDate> {
    private int year;
    private int month;
    private int day;

    public MyDate() {
    }

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    //没有指明泛型类型时的写法,默认泛型类型为Object类型
//    @Override
//    public int compareTo(Object o) {
//        if (o instanceof MyDate) {
//            MyDate m = (MyDate) o;
//            int sumYear = Integer.compare(this.getYear(), m.getYear());
//            if (sumYear != 0) {
//                return sumYear;
//            }
//            int sumMonth = Integer.compare(this.getMonth(), m.getMonth());
//            if (sumMonth != 0) {
//                return sumMonth;
//            }
//            return Integer.compare(this.getDay(), m.getDay());
//        }
//        throw new RuntimeException("输入数据类型不一致");
//    }

    //指明泛型类型时的写法:无需instanceof判断和强制类型转换,也不用抛异常了
    @Override
    public int compareTo(MyDate o) {
        int sumYear = Integer.compare(this.getYear(), o.getYear());
        if (sumYear != 0) {
            return sumYear;
        }
        int sumMonth = Integer.compare(this.getMonth(), o.getMonth());
        if (sumMonth != 0) {
            return sumMonth;
        }
        return Integer.compare(this.getDay(), o.getDay());
    }
}

三、自定义泛型结构

1. 自定义泛型类 / 泛型接口

1.1 声明泛型类 / 泛型接口
//泛型类
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {}

//泛型接口
public interface List<E> extends Collection<E> {}
  1. 泛型的泛型参数,如T, E, K, V等,用尖括号括起来,放在类 / 接口的声明处(类名 / 接口名之后)
  2. 泛型类 / 泛型接口可能有多个泛型参数,此时应将多个泛型参数一起放在尖括号内
  3. 一旦指定类 / 接口的泛型,在类 / 接口的内部就可以将泛型参数作为非静态属性的类型、非静态方法的形参类型、非静态方法的返回值类型、类的构造器的形参类型。但==静态方法中不能使用类的泛型参数==
    • 类的泛型是在实例化类的对象时指定的,而静态结构是随着类的加载而加载的,生命周期不同
  4. 异常类(继承于Exception或RuntimeException)不能是泛型的
  5. 在继承泛型父类时,子类可以选择指定泛型类型,也可以选择保留泛型,还可以增加额外的泛型参数
    • 子类不保留父类的泛型:按需实现
      • 没有类型 擦除
      • 指明具体的类型
    • 子类保留父类的泛型:泛型子类
      • 全部保留
      • 部分保留
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father{
}
// 2)指明具体的类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}
  • 子类增加额外的泛型参数
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father{//等价于class Son extends Father{
}
// 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> {
}
1.2 实例化泛型类 / 泛型接口的泛型实现类
  1. 实例化泛型类时,在类名后声明一个类型实参"<指定类型>",显式指定泛型类型
  2. JDK7.0新特性——类型推断:ArrayList list = new ArrayList<>();
  3. 指定泛型类型后,在泛型类内部,凡是使用到泛型参数的位置(如:某个属性的类型、某个方法的返回值或形参类型、某个构造器的形参类型)都会被指定为该泛型类型
  4. 泛型类型必须是一个类,不能是基本数据类型,但可以是它们的包装类
  5. 如果实例化泛型类时,没有指明泛型类型(泛型参数的具体类型),则默认为java.lang.Object类型
  6. 泛型不同的引用类型变量不能相互赋值
  7. 对于泛型参数T,T[] arr = new T[10]; 是非法的;而 T[] arr = (T[]) new Object[10]; 是合法的

2. 自定义泛型方法

  1. 泛型方法:在方法声明时定义了独立于类的泛型参数。泛型方法的泛型参数与当前类是不是泛型类无关,与当前类的泛型参数也无关
  2. 泛型方法的格式:访问权限 <泛型标识> 返回类型 方法名(泛型标识 形参名) 抛出的异常 {}
public <E> List<E> copyFromArrayToList(E[] arr) {}
  1. 在类的外部调用泛型方法时,泛型参数的类型一般会根据传入形参的类型进行推断
  2. 泛型方法可以声明为静态的:泛型方法的泛型参数是在调用方法时确定的,并非在实例化类的对象时确定,即==静态方法中不能使用类的泛型参数,但泛型方法可以是静态的==
import org.junit.Test;

public class OrderTest {
    @Test
    public void test1() {
        //如果实例化泛型类时,没有指明泛型类型(泛型参数的具体类型),则默认为java.lang.Object类型
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("ABC");

        //要求:如果在定义类时声明了泛型泛型参数,则在实例化类的对象时,要指明泛型类型(类型实参)
        Order<String> stringOrder = new Order<>("orderAA", 1001, "order:AA");
        stringOrder.setOrderT("ABC");
    }

    @Test
    public void test2() {
        //若子类在继承泛型父类时,指定了泛型类型,则实例化子类对象时,不再需要指明类型实参
        SubOrder sub1 = new SubOrder();
        sub1.setOrderT(1122);

        //若子类在继承泛型父类时,保留了父类的泛型,则实例化子类对象时,仍需要指明类型实参
        SubOrder1<String> sub2 = new SubOrder1<>();
        sub2.setOrderT("order2");
    }
    
    //泛型方法
    @Test
    public void test3() {
        Order<String> order = new Order<>();
        Integer[] arr = new Integer[]{1, 2, 3, 4};
        //泛型方法只有在调用时,才指明泛型参数的类型,与类的实例化无关
        List<Integer> list = order.copyFromArrayToList(arr);
        System.out.println(list);
    }
}
public class Order<T> {
    String orderName;
    int orderId;

    //声明一个泛型类型的属性
    T orderT;

    //声明一个具有泛型类型形参的构造器
    public Order(String orderName, int orderId, T orderT) {
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }

    //******************* 如下的方法都不是泛型方法 *******************
    //提供泛型类型属性的get/set方法
    //方法的返回值是泛型类型的
    public T getOrderT() {
        return orderT;
    }
    //方法的形参是泛型类型的
    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }

    //重写toString()方法
    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }

    //************************** 泛型方法 **************************
    public static <E> List<E> copyFromArrayToList(E[] arr) {
        ArrayList<E> list = new ArrayList<>();
        for (E e : arr) {
            list.add(e);
        }
        return list;
    }
}
//若子类在继承泛型父类时,指定了泛型类型,则子类不再是泛型类,不再具有泛型参数
public class SubOrder extends Order<Integer> {
}
//若子类在继承泛型父类时,保留了父类的泛型,则子类仍然是一个泛型类
public class SubOrder1<T> extends Order<T> {
}

3. 泛型类和泛型方法的使用情境

练习1
  • 数据库不同表的增删改查操作:ORM思想(数据库中的一张表就对应Java中的一个类)
import org.junit.Test;

public class DAOTest {
    @Test
    public void test1() {
        CustomerDAO dao1 = new CustomerDAO();
        dao1.add(new Customer());

        StudentDAO dao2 = new StudentDAO();
        dao2.add(new Student());
    }
}
import java.util.List;

//DAO: Data(base) Access Object 数据访问对象
//泛型类:最基本的增删改查的封装,具体操作哪张表(哪个类的对象)不确定
public class DAO<T> {

    //添加一条记录
    public void add(T t) {

    }
    //删除一条记录
    public boolean remove(int index) {
        return false;
    }
    //修改一条记录
    public void update(int index,  T t) {

    }
    //查询一条记录
    public T get(int index) {
        return null;
    }
    //查询多条记录
    public List<T> getForList(int index) {
        return null;
    }

    //泛型方法
    //举例:获取表中一共有多少条记录 --> 方法的泛型参数E一般指定为 Long
    //获取最大的员工入职时间 --> 方法的泛型参数E一般指定为 Date
    public <E> E getValue() {
        return null;
    }
}
//子类指定了父类的泛型类型,只能操作customers表的增删改查
public class CustomerDAO extends DAO<Customer> {}
//子类指定了父类的泛型类型,只能操作students表的增删改查
public class StudentDAO extends DAO<Student> {}
//此类对应数据库中的customers表
public class Customer {}
//此类对应数据库中的students表
public class Student {}
练习2
  1. 定义一个泛型类 DAO,在其中定义一个 Map 成员变量,Map 的键为 String 类型,值为 T 类型。
  2. 分别创建以下方法:
    • public void save(String id,T entity): 保存 T 类型的对象到 Map 成员变量中
    • public T get(String id):从 map 中获取 id 对应的对象
    • public void update(String id,T entity):替换 map 中 key 为 id 的内容,改为 entity 对象
    • public List list():返回 map 中存放的所有 T 对象
    • public void delete(String id):删除指定 id 对象
  3. 定义一个 User 类:
    该类包含:private 成员变量(int 类型)id,age;(String 类型)name
  4. 定义一个测试类:
    创建 DAO 类的对象,分别调用其 save、get、update、list、delete方法来操作User对象,使用Junit单元测试
import java.util.*;

public class DAO<T> {
    private Map<String, T> map;

    public DAO() {
    }

    public DAO(Map<String, T> map) {
        this.map = map;
    }

    //保存 T 类型的对象到 Map 成员变量中
    public void save(String id, T entity) {
        map.put(id, entity);
    }

    //从 map 中获取 id 对应的对象
    public T get(String id) {
        return map.get(id);
    }

    //替换 map 中 key 为 id 的内容,改为 entity 对象
    public void update(String id,T entity) {
        if (map.containsKey(id)) {
            map.put(id, entity);
        }
    }

    //返回 map 中存放的所有 T 对象
    public List<T> list() {
        //错误的写法:values()方法本来的返回值类型就是Collection,父类类型的对象不能强转为子类类型
//        Collection coll = map.values();
//        return (List) coll;

        //正确的写法:
        List<T> list = new ArrayList<>(); //多态
        Collection<T> coll = map.values();
        Iterator<T> iterator = coll.iterator();
        while (iterator.hasNext()) {
            T value = iterator.next();
            list.add(value);
        }
        return list;
    }

    //删除指定 id 对象
    public void delete(String id) {
        map.remove(id);
    }
}
public class User {
    private int id, age;
    private String name;

    public User() {
    }

    public User(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (id != user.id) return false;
        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }
}
import org.junit.Test;
import java.util.HashMap;
import java.util.List;

public class DAOTest {
    @Test
    public void test() {
        DAO1<User> dao = new DAO<>(new HashMap<>());
        dao.save("1001", new User(1001, 34, "周杰伦"));
        dao.save("1002", new User(1002, 20, "昆凌"));
        dao.save("1003", new User(1003, 25, "蔡依林"));

        dao.update("1001", new User(1001, 55, "方文山"));

        dao.delete("1002");

        List<User> list = dao.list();
        list.forEach(System.out::println);
    }
}

四、泛型在继承上的体现

  1. 如果B是A的子类或者子接口,而G是具有泛型声明的类或接口, G并不是G的子类型