Java基础知识-第20章--Java泛型

190 阅读26分钟

1、泛型概述

泛型引入背景

集合容器类在设计阶段或声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把集合里面的元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型Collection<E>List<E>ArrayList<E> 这个<E>就是类型参数,即泛型。他会影响后面的方法调用,即传入参数必须是指定的E类型的,即如果一个类定义了泛型,则会影响到后面使用了这个泛型参数的一些结构,比如方法里面的形参类型,因为形参类型用的是泛型。这部分会在后面细致的讲道。

泛型概念

从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念,Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如泛型这种参数化类型机制允许我们在创建集合时再指定集合元素的类型,正如:List<String>,这表明该List只能保存字符串类型的对象。

JDK1.5 改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(如方法调用时只能传入E这个类型的形参,或继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)

举例

假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?

答案是可以使用 Java 泛型。使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。

2、泛型举例

2.1、ArrayList

在集合中使用泛型之前的情况:(用object接收各种类型的参数)

@Test
public void test1(){
   ArrayList list = new ArrayList();
   //需求:存放学生的成绩
   list.add(78); //自动装箱,没有泛型add方法接收的是object类型的参数
   list.add(76);
   list.add(89);
   list.add(88);
   //问题一:类型不安全,没有泛型约束就可能传入一些其他类型数据
   //list.add("Tom");

   for(Object score : list){
       //问题二:强转时,可能出现ClassCastException
       int stuScore = (Integer) score;//自动拆箱,score是Object类型的,要强转再自动拆箱
       System.out.println(stuScore); 
   }
}

图解

image.png

在集合中使用泛型例子1

因此泛型的话,解决了上面的两个问题

  • 解决元素存储的安全性问题;
  • 解决获取数据元素时,需要类型强制准换的问题,即Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException异常。同时,代码更加简洁、健壮。

代码展示

@Test
public void test2(){
    //使用泛型
    ArrayList<Integer> list =  new ArrayList<Integer>();
    //新特性:ArrayList<Integer> list =  new ArrayList<>();

    list.add(78);
    list.add(87);
    list.add(99);
    list.add(65);
    //编译时,就会进行类型检查,保证数据的安全
    //list.add("Tom");

    //方式一:增强for循环
    for(Integer score : list){
        //避免了强转操作,自动拆箱,score就是interger类型的
        int stuScore = score;
        System.out.println(stuScore);
    }

    //方式二:迭代器,因为iterator方法的返回类型定义了泛型E,且Iterator接口也声明了泛型E
    //且ArrayList指明了E为Integer
    Iterator<Integer> iterator = list.iterator();//迭代器拿值
    while(iterator.hasNext()){
        int stuScore = iterator.next(); //得到的就是interger,next()方法返回的就是泛型类型E
        System.out.println(stuScore);
    }
}

图解:

image.png

注意:泛型参数<E> 不能是基本数据类型,必须是包装类或者其他类

2.2、HashMap

在集合中使用泛型例子2

//在集合中使用泛型的情况:以HashMap为例
@Test
public void test3(){
    
    //Map<String,Integer> map = new HashMap<String,Integer>();
    //jdk7新特性:类型推断
    Map<String,Integer> map = new HashMap<>();

    map.put("Tom",87);//已经声明了类型了,所以必须赋这种类型的参数
    map.put("Jerry",87);
    map.put("Jack",67);

    //泛型的嵌套,Entry是Map的内部接口,并且我们在HashMap里面声明了泛型<String,Integer>
    //所以各个方法的返回值也会受到泛型的影响
    Set<Map.Entry<String,Integer>> entry = map.entrySet();
    Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();

    while(iterator.hasNext()){
        Map.Entry<String, Integer> e = iterator.next();
        String key = e.getKey();
        Integer value = e.getValue();
        System.out.println(key + "----" + value);
    }
}

2.3、总结

集合接口或集合类在jdk5.0时都修改为带泛型的结构,在实例化集合类时,可以指明具体的泛型类型,指明完泛型以后,内部结构(比如:方法的返回值类型、构造器、属性等)使用到类的泛型的位置,都会受到该泛型的影响,即都会指定为实例化时候指定的泛型类型。

比如:ArrayList类,声明了泛型<E>,即

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

而它内部的方法都使用到了这个泛型,比如add(E e)

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

所以当我们用Integer实例化以后,参数E就是Integer,因此这个Integer泛型就会影响到我们的add方法,如下:

ArrayList<Integer> list =  new ArrayList<Integer>()

add(E e)  --->实例化以后:add(Integer e)  

Iterator<Integer> iterator = list.iterator();  

能直接这样写的话是因为Iterator接口也声明了泛型,如下

public interface Iterator<E> {

itertor()方法的返回值也是声明了泛型,如下

public Iterator<E> iterator() {
    return new Itr();
}

因为List 将泛型声明为了Integer,所以iterator方法返回的Iterator类型自然是Iterator<Integer>

注意点:

  • 泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
  • 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

3、集合中使用泛型

3.1、实例

  • 定义一个 Employee 类, 该类包含:private 成员变量 name,age,birthday,其中 birthday 为 MyDate 类的 对象; 并为每一个属性定义 getter, setter 方法; 并重写 toString 方法输出 name, age, birthday
  • MyDate 类包含:private 成员变量 month,day,year;并为每一个属性定义 getter, setter 方法;
  • 创建Employee 类的 5 个对象,并把这些对象放入 TreeSet 集合中(TreeSet 需使用泛型来定义),
  • 分别按以下两种方式对集合中的元素进行排序,并遍历输出:
    • 使 Employee 继承 Comparable 接口,并按 name 排序
    • 创建 TreeSet 时传入 Comparator 对象,按生日日期的先后排序

3.2、代码实现

Employee 类

public class Employee implements Comparable<Employee>{
    private String name;
    private int age;
    private MyDate birthday;

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

    public Employee() {
    }

    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 + '}';
    }

    @Override
    public int compareTo(Employee o) {
        return this.name.compareTo(o.name);
    }
}

MyDate类

public class MyDate implements Comparable<MyDate>{
    private int year;
    private int month;
    private int day;

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

    public MyDate() {
    }

    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 + '}';
    }

    @Override
    public int compareTo(MyDate m) {
        //比较年
        int minusYear = this.getYear() - m.getYear();
        if(minusYear != 0){
            return minusYear;
        }
        //比较月
        int minusMonth = this.getMonth() - m.getMonth();
        if(minusMonth != 0){
            return minusMonth;
        }
        //比较日
        return this.getDay() - m.getDay();
    }
}

使用自然排序

@Test
public void test1(){
    
    TreeSet<Employee> set = new TreeSet<Employee>();

    Employee e1 = new Employee("liudehua",55,new MyDate(1965,5,4));
    Employee e2 = new Employee("zhangxueyou",43,new MyDate(1987,5,4));
    Employee e3 = new Employee("guofucheng",44,new MyDate(1987,5,9));
    Employee e4 = new Employee("liming",51,new MyDate(1954,8,12));
    Employee e5 = new Employee("liangzhaowei",21,new MyDate(1978,12,4));

    set.add(e1);
    set.add(e2);
    set.add(e3);
    set.add(e4);
    set.add(e5);

    Iterator<Employee> iterator = set.iterator();
    while (iterator.hasNext()){
        Employee employee = iterator.next();
        System.out.println(employee);
    }
    //Employee{name='guofucheng', age=44, birthday=MyDate{year=1987, month=5, day=9}}
    //Employee{name='liangzhaowei', age=21, birthday=MyDate{year=1978, month=12, day=4}}
    //Employee{name='liming', age=51, birthday=MyDate{year=1954, month=8, day=12}}
    //Employee{name='liudehua', age=55, birthday=MyDate{year=1965, month=5, day=4}}
    //Employee{name='zhangxueyou', age=43, birthday=MyDate{year=1987, month=5, day=4}}
}

按生日日期的先后排序

@Test
public void test2(){

    TreeSet<Employee> set = new TreeSet<>(new Comparator<Employee>() {
        @Override
        public int compare(Employee o1, Employee o2) {
            MyDate b1 = o1.getBirthday();
            MyDate b2 = o2.getBirthday();
            return b1.compareTo(b2);
        }
    });

    Employee e1 = new Employee("liudehua",55,new MyDate(1965,5,4));
    Employee e2 = new Employee("zhangxueyou",43,new MyDate(1987,5,4));
    Employee e3 = new Employee("guofucheng",44,new MyDate(1987,5,9));
    Employee e4 = new Employee("liming",51,new MyDate(1954,8,12));
    Employee e5 = new Employee("liangzhaowei",21,new MyDate(1978,12,4));

    set.add(e1);
    set.add(e2);
    set.add(e3);
    set.add(e4);
    set.add(e5);

    Iterator<Employee> iterator = set.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
    //Employee{name='liming', age=51, birthday=MyDate{year=1954, month=8, day=12}}
    //Employee{name='liudehua', age=55, birthday=MyDate{year=1965, month=5, day=4}}
    //Employee{name='liangzhaowei', age=21, birthday=MyDate{year=1978, month=12, day=4}}
    //Employee{name='zhangxueyou', age=43, birthday=MyDate{year=1987, month=5, day=4}}
    //Employee{name='guofucheng', age=44, birthday=MyDate{year=1987, month=5, day=9}}
}

4、自定义泛型类、泛型接口

4.1、泛型类

自定义泛型类测试

泛型类是在实例化的时候指明泛型的具体类型。

自定义泛型类:Order

//自定义一个泛型类
public class Order<T>{
    String orderName;
    int orderId;
    //该属性类型不确定,类的内部结构就可以使用类的泛型
    T orderT;

    public Order(){
    }
    
    public Order(String orderName, int orderId, T orderT) {
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }
    public T getOrderT() {
        return orderT;
    }
    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }
    @Override
    public String toString() {
        return "Order{" +
            "orderName='" + orderName + '\'' +
            ", orderId=" + orderId +
            ", orderT=" + orderT +
            '}';
    }
}

泛型的实例化:

一定要在类名后面指定类型参数的值(类型)。如:

List<String> strList = new ArrayList<String>();
Iterator<Customer> iterator = customers.iterator()
  • T只能是类,不能用基本数据类型填充。但可以使用包装类填充
  • 一个集合中的内容限制为一个特定的数据类型,这就是generics背后的核心思想

测试泛型类:

//自定义泛型类、泛型接口测试
public class GenericTest1 {
    
    @Test
    public void test1(){
        
        //如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型T为Object类型
        //如果定义的类是带泛型的,建议在实例化时就要指明类的泛型
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("ABC");//无论什么类型都可以设置

        //实例化时建议指明类的泛型,为String
        Order<String > order = new Order<>("orderAA",1001,"order:AA");
        order.setOrderT("AA:hello");
    }
}

子类继承泛型类

假设SubOrder类和SubOrder1类都分别继承了泛型类Order<T>,这时候就要分为两种情况:

  • 情况1:继承的时候指明泛型<T>Integer
public class SubOrder extends Order<Integer>{
}

这时候,我们实例化子类的对象,如下:SubOrder类其实就是一个普通的类

public void test2(){
    
    //由于子类在继承带泛型的父类时,指明了泛型类型
    //所以实例化子类对象时,不再需要指明泛型
    SubOrder sub1 = new SubOrder();
    sub1.setOrderT(1122);
}
  • 情况2:继承的时候没有指明泛型<T>
public class SubOrder1<T> extends Order<T>{
}

这时候 SubOrder1仍然是一个泛型类,这样的话就和ArrayList机制是一样的,在造这个子类对象的时候就能更加灵活。

public void test2(){
    SubOrder1<String> sub2 = new SubOrder1<>();
    sub2.setOrderT("order2...");
}

4.2、泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

import java.util.Random;
public class GenericTest {
    //定义一个泛型接口
    interface Generator<T> {
        public T next();
    }
    /**
     * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
     * 即:class FruitGenerator<T> implements Generator<T>{
     * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
     */
    static class FruitGenerator<T> implements Generator<T>{
        @Override
        public T next() {
            return (T)"a";
        }
    }

    /**
     * 传入泛型实参时:
     * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
     * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
     * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
     * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
     */
    static class FruitGenerator1 implements Generator<String> {
        private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
        @Override
        public String next() {
            Random rand = new Random();
            return fruits[rand.nextInt(3)];
        }
    }
    public static void main(String args[]){
        FruitGenerator <Integer> fruitGenerator=new FruitGenerator<Integer>();
        System.out.println( fruitGenerator.next());
        FruitGenerator1 fruitGenerator1=new FruitGenerator1();
        System.out.println( fruitGenerator1.next());
    }
}

4.3、泛型类注意点

  • 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
  • 泛型类的构造器如下 public GenericClass(){},而这种是错误的: public GenericClass<E>(){}
  • 实例化泛型类对象后,操作原来泛型位置的结构必须与指定的泛型类型一致。
  • 泛型不同的引用不能相互赋值。
@Test
public void test3(){
    ArrayList<String> list1 = null;
    ArrayList<Integer> list2 = new ArrayList<Integer>();
    //泛型不同的引用不能相互赋值,除非没有泛型
    //list1 = list2;
}
  • 尽管在编译时 ArrayList<String>ArrayList<Integer> 是两种类型,但是,在运行时只有一个 ArrayList 被加载到 JVM 中 。
  • 泛型如果不指定,将被擦除,泛型对应的类型均按照 Object 处理,但不等价于 Object 。 经验:泛型要使用一路都用。要不用,一路都不要用。
  • 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
  • jdk1.7泛型的简化操作 ArrayList<Fruit > flist = new ArrayList<>();
  • 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
  • 在类、接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型,因为静态结构的加载要早于对象的创建,所以在泛型类对象并未实例化的时候,即未指明泛型就使用就静态方法报错
//自定义一个泛型类
public class Order<T>{
  String orderName;
  int orderId;
  //类的内部结构就可以使用类的泛型
  T orderT;

  //在静态方法中不能使用类的泛型,下面报错
  public static void show(T orderT){ 
      System.out.println(orderT);
  }
}
  • 自定义异常类不能是泛型的
  • 不能使用 new E[]。但是可以 E[] elements = (E[])new Object[capacity];
public Order(){
    //编译不通过
    T[] arr = new T[10];
    //编译通过
    T[] arr = (T[]) new Object[10];
}
  • 参考:ArrayList 源码中声明: Object[] elementData 而非泛型参数类型数组。
  • 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
    • 子类不保留父类的泛型:按需实现
      • 没有类型 擦除
      • 具体类型
    • 子类保留父类的泛型:泛型子类
      • 全部保留
      • 部分保留

举例

class Father<T1, T2> { 
} 
// 子类不保留父类的泛型 
// 1)没类型 擦除 
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{ 
} 
// 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<Object,Object>{ 
} 
// 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> { 
}

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

5、自定义泛型方法

方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型 方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。

泛型方法的格式:

[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常 

泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 <E>)。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)。

java 中泛型标记符:

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • - 表示不确定的 java 类型

5.1、定义泛型方法

定义泛型方法时,必须在返回值类型之前加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。

  • 泛型方法:在方法中出现了泛型的结构,方法中的泛型参数与类的泛型参数没有任何关系。
  • 换句话说,泛型方法所属的类是不是泛型类都没有关系。
  • **注意:**泛型方法,可以声明为静态的。原因:泛型方法的参数是在调用方法时确定的。并非在实例化类时确定。

图解:

image.png

如下的三个方法都不是泛型方法

//如下的三个方法都不是泛型方法
public T getOrderT(){
   return orderT;
}

public void setOrderT(T orderT){
   this.orderT = orderT;
}

下面的就是一个泛型方法

public class Order<T> {

    String orderName;
    int orderId;
    //类的内部结构就可以使用类的泛型
    T orderT;

    //泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。类的泛型为T,方法的泛型为E
    public static <E>  List<E> copyFromArrayToList(E[] arr){
        ArrayList<E> list = new ArrayList<>();
        for(E e : arr){
            list.add(e);
        }
        return list;
    }
}

因此对于泛型方法来说,方法上定义的泛型参数<E>可以是任何类型,与我们的泛型类上的泛型<T>没有任何关系,但是在定义泛型方法时,必须在返回值类型之前加一个<E>,来声明这是一个泛型方法,持有一个泛型E,然后才可以用泛型E作为方法的返回值,否则就会认为<E>是一个类型参数,而泛型方法是在对象调用方法的时候指明泛型的具体类型。

比如我们的ArrayList里面的一个方法,就是属于一个泛型方法,

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

当通过对象调用该泛型方法的时候:可以看出造对象用的是Integer泛型,而调用泛型方法的传递的是String类型

ArrayList<Integer> list = new ArrayList<>();
//Integer集合转化成Stirng数组类型
String[] arr = new String[]{"1","2","3","4"};
String[] list1 = list.toArray(String[] a);
System.out.println(list1);

5.2、调用泛型方法

图解

image.png

泛型方法是在调用方法的时候指明泛型的具体类型,也说明了方法上定义的泛型参数<E>可以是任何类型,与我们的泛型类上的泛型<T>没有任何关系

//测试泛型方法
public void test4(){
    Order<String> order = new Order<>();
    Integer[] arr = new Integer[]{1,2,3,4};
    //泛型方法在调用时,指明泛型参数的类型
    List<Integer> list = order.copyFromArrayToList(arr);
    System.out.println(list);//[1, 2, 3, 4]
}

6、自定义泛型类练习

1.定义个泛型类 DAO<T>,在其中定义一个Map 成员变量,Map 的键为 String 类型,值为 T 类型。
    分别创建以下方法:
    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<T> list():返回 map 中存放的所有 T 对象
    public void delete(String id):删除指定 id 对象
    
2.定义一个 User 类:
    该类包含:private成员变量(int类型) id,age;(String 类型)name。
    
3.创建 DAO 类的对象, 分别调用其 save、get、update、list、delete 方法来操作 User 对象,
    使用 Junit 单元测试类进行测试。

6.1、DAO.java

DAO:data(base) access object,和数据表里面的增删改查一样,即表的共性操作的DAO

//T:不知道要操作的是那个表,表对应一个类,ORM思想
public class DAO<T> {
    private Map<String,T> map = new HashMap<String,T>();
    
    //保存 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(){
        //错误的:
        //Collection<T> values = map.values();
        //return (List<T>) values;
        //正确的:
        ArrayList<T> list = new ArrayList<>();
        Collection<T> values = map.values();
        for(T t : values){
            list.add(t);
        }
        return list;
    }
    
    //删除指定 id 对象
    public void delete(String id){
        map.remove(id);
    }
}

6.2、User.java

public class User {
    private int id;
    private int age;
    private String 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;
    }

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

    public User() {
    }

    @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;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + age;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
}

6.3、DAOTest.java

public class DAOTest {
    public static void main(String[] args) {
        //泛型参数T为user,
        DAO<User> dao = new DAO<User>();

        dao.save("1001",new User(1001,34,"周杰伦"));
        dao.save("1002",new User(1002,20,"昆凌"));
        dao.save("1003",new User(1003,25,"蔡依林"));
        dao.update("1003",new User(1003,30,"方文山"));
        dao.delete("1002");

        List<User> list = dao.list();
        list.forEach(System.out::println);
        //User{id=1003, age=30, name='方文山'}
        //User{id=1001, age=34, name='周杰伦'}
    }
}

7、泛型在继承方面的体现

虽然类A是类B的父类,而G是具有泛型声明的类或接口,但是G<A>G<B>二者不具备子父类关系,二者是并列关系。

比如:String是Object的子类,但是List<String > 并不是List<object> 的子类。

@Test
public void test1(){

    Object obj = null;
    String str = null;
    obj = str;//编译通过,这是子父类多态的体现

    Object[] arr1 = null;
    String[] arr2 = null;
    arr1 = arr2; //编译通过,这是子父类多态的体现
    
    
    //编译不通过,两者没有任何关系
    //Date date = new Date();
    //str = date;
    
    List<Object> list1 = null;
    List<String> list2 = new ArrayList<String>();
    //此时的list1和list2的类型不具子父类关系
    //编译不通过
    //list1 = list2;
}

补充:类A是类B的父类,A<G>B<G> 的父类

@Test
public void test2(){
    AbstractList<String> list1 = null;
    List<String> list2 = null;
    ArrayList<String> list3 = null;
    list1 = list3;
    list2 = list3;
    List<String> list4 = new ArrayList<>();
}

8、通配符

  • 使用类型通配符:, 比如:List<?> Map<?,?>,其中List<?> List<String > List<object>等各种泛型List的父类。
  • 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object。
  • 写入List<?>中的元素时,编译时会报错。因为我们不知道集合的元素类型,我们不能向其中添加对象。唯一的例外是null,它是所有类型的成员。

8.1、通配符的使用

通配符:?,类A是类B的父类,G<A>G<B>是没关系的,二者共同的父类是:G<?>

@Test
public void test3(){
    
    List<Object> list1 = null;
    List<String> list2 = null;

    List<?> list = null;//将泛型声明为通配符?

    list = list1;
    list = list2;
    //编译通过
    print(list1);
    print(list2);
}

public void print(List<?> list){
    Iterator<?> iterator = list.iterator();
    while(iterator.hasNext()){
        Object obj = iterator.next();
        System.out.println(obj);
    }
}

8.2、涉及通配符的集合的数据的写入和读取

@Test
public void test4(){

    List<?> list = null;//将泛型声明为通配符?
    List<String> list3 = new ArrayList<>();
    list3.add("AA");
    list3.add("BB");
    list3.add("CC");
    list = list3;

    //添加(写入):对于List<?>就不能向其内部添加数据。
    //除了添加null之外。
    //list.add("DD");
    //list.add('?');

    list.add(null);

    //获取(读取):允许读取数据,读取的数据类型一定可以赋给Object。
    Object o = list.get(0);
    System.out.println(o);
}

8.3、通配符使用的注意点

//注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){
}

//注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{
}

//注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>();

8.4、有限制条件的通配符的使用

  • <?>允许所有泛型的引用调用
  • 通配符指定上限
    • 上限extends:使用该通配符时指定的类型必须是继承某个类,或者实现某个接口,即<=
  • 通配符指定下限
    • 下限super:使用时指定的类型不能小于操作的类,即>=
  • 举例:
    • <? extend Number> 范围区间为(无穷小 , Number] ,只允许泛型为Number及Number子类的引用调用
    • <? Super Number> 范围区间[Number , 无穷大) ,只允许泛型为Number及Number父类的引用调用
    • <? extend Comparable>只允许泛型为实现Comparable接口的实现类的引用调用

有限制条件的通配符的使用:

  • <? extends A>: (<=A)的情况下, G<? extends A> 可以作为G<A>G<B>的父类,其中B是A的子类
  • <? super A>: (>=A)的情况下, G<? super A> 可以作为G<A>G<B>的父类,其中B是A的父类
//Student extends Person extends Object
@Test
public void test4(){

    List<? extends Person> list1 = null;
    List<? super Person> list2 = null;

    List<Student> list3 = new ArrayList<Student>();
    List<Person> list4 = new ArrayList<Person>();
    List<Object> list5 = new ArrayList<Object>();

    //list3,list4可以赋给list1,list5不行
    list1 = list3;  
    list1 = list4;
    //list1 = list5;  object太大了

    //list2 = list3;  student太小了
    list2 = list4;
    list2 = list5;

    //读取数据:
    list1 = list3;  
    //list1不会超过Person ,Person接收可以
    Person p = list1.get(0);
    //编译不通过,list1最小是用Person接收,因为万一返回一个Person类型的。不能用子类Student接收
    //Student s = list1.get(0);
    
    list2 = list4; 
    Object obj = list2.get(0);
    //编译不通过 list2只能用Object接收 万一返回一个object类型的。不能用子类Person接收
    //Person obj = list2.get(0);

    //写入数据:
    //编译不通过  万一?添加的数据类型比Student还小 (父类可以赋给子类,子类不可以赋给父类)
    //list1.add(new Student());

    //编译通过,因为最大的就是可以使用object接收
    list2.add(new Person());
    list2.add(new Student());
}	 

练习题:

image.png

9、Java类库中的泛型

所有的标准集合接口都是泛型化的—— Collection<V>List<V>Set<V> 和 Map<K,V>。

类似地,集合接口的实现都是用相同类型参数泛型化的,HashMap<K,V> 实现 Map<K,V> 等。

10、练习

1、泛型方法

下面的例子演示了如何使用泛型方法打印不同类型的数组元素:

public class GenericMethodTest{
    // 泛型方法 printArray                         
    public static < E > void printArray( E[] inputArray ){
        // 输出数组元素            
        for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
        }
        System.out.println();
    }

    public static void main( String args[] ){
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

        System.out.println( "整型数组元素为:" );
        printArray( intArray  ); // 传递一个整型数组

        System.out.println( "\n双精度型数组元素为:" );
        printArray( doubleArray ); // 传递一个双精度型数组

        System.out.println( "\n字符型数组元素为:" );
        printArray( charArray ); // 传递一个字符型数组
    } 
}

编译以上代码,运行结果如下所示:

整型数组元素为:
1 2 3 4 5 

双精度型数组元素为:
1.1 2.2 3.3 4.4 

字符型数组元素为:
H E L L O 

2、有界的类型参数

可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。

要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。

下面的例子演示了"extends"如何使用在一般意义上的意思"extends"(类)或者"implements"(接口)。该例子中的泛型方法返回三个可比较对象的最大值。 <T extend Comparable>只允许泛型为实现Comparable接口的实现类的引用调用

public class MaximumTest{
    // 比较三个值并返回最大值
    public static <T extends Comparable<T>> T maximum(T x, T y, T z){                     
        T max = x; // 假设x是初始最大值
        if ( y.compareTo( max ) > 0 ){
            max = y; //y 更大
        }
        if ( z.compareTo( max ) > 0 ){
            max = z; // 现在 z 更大           
        }
        return max; // 返回最大对象
    }
    public static void main( String args[] ){
        System.out.printf( "%d, %d 和 %d 中最大的数为 %d\n\n",
                          3, 4, 5, maximum( 3, 4, 5 ) );

        System.out.printf( "%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n",
                          6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );

        System.out.printf( "%s, %s 和 %s 中最大的数为 %s\n","pear",
                          "apple", "orange", maximum( "pear", "apple", "orange" ) );
    }
}

编译以上代码,运行结果如下所示:

3, 4 和 5 中最大的数为 5

6.6, 8.8 和 7.7 中最大的数为 8.8

pear, apple 和 orange 中最大的数为 pear

3、类型通配符

import java.util.*;

public class GenericTest {
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();

        name.add("icon");
        age.add(18);
        number.add(314);

        getData(name);
        getData(age);
        getData(number);
    }

    public static void getData(List<?> data) {
        System.out.println("data :" + data.get(0));
    }
}

输出结果为:

data :icon
data :18
data :314

解析: 因为 getData() 方法的参数是 List 类型的,所以 **name,age,number** 这三个实现类都可以作为List 的子类,所以可以作为这个方法的实参,这就是通配符的作用。

4、有限制条件的通配符

类型通配符上限通过形如List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。

import java.util.*;

public class GenericTest {

    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();

        name.add("icon");
        age.add(18);
        number.add(314);

        //getUperNumber(name);//1
        getUperNumber(age);//2
        getUperNumber(number);//3

    }

    //但是get方法返回的类型最小只能用Number接收,因为万一返回一个number类型的,student不能接收
    public static void getUperNumber(List<? extends Number> data) {
        System.out.println("data :" + data.get(0));
    }
}

输出结果:

data :18
data :314 

解析: 在 //1 处会出现错误,因为 getUperNumber() 方法中的参数已经限定了参数泛型上限为 Number,所以泛型为 String 是不在这个范围之内,所以会报错。

类型通配符下限通过形如 List<? super Number> 来定义,表示类型只能接受 Number 及其上层父类类型,如 Object 类型的实例