10.泛型


1.泛型的理解

1.泛型的概念

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。****泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

本质:编译时检查类型是否一致,就像药瓶上的标签,起到标识类型的作用;但是在运行时,泛型已经被擦除(JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的),不起任何作用。

2.泛型的引入背景

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,

JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,

例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。

代码示例:

List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){
    String item = (String)arrayList.get(i);
    log.info("泛型测试","item = " + item);
}
//java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。

eg:Collection,List,ArrayList 这个就是类型参数,即泛型。

3.特性:泛型只在编译阶段有效

代码示例:

List stringArrayList = new ArrayList();
List integerArrayList = new ArrayList();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
    log.info("泛型测试","类型相同");
}

输出结果:D/泛型测试: 类型相同。

通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

总结:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

2.泛型在集合中的使用

image-20220110074049138 image-20220110074108083

集合中使用泛型总结:

  • ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
  • ② 在实例化集合类时,可以指明具体的泛型类型
  • ③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
  • 比如:add(E e) --->实例化以后:add(Integer e)
  • ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
  • ⑤ 如果实例化时,没指明泛型的类型。默认类型为java.lang.Object类型

3.自定义泛型类、泛型接口、泛型方法

注意点总结

1.泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
2.泛型类的构造器如下:public GenericClass(){}。(构造器不带泛型
而下面是错误的:public GenericClass(){}
3.实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
4.泛型不同的引用不能相互赋值。 eg: ArrayList list1; ArrayList list2; list1 = list2; 不可以这样辅助
尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载JVM中
5.泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
经验:泛型要使用一路都用。要不用,一路都不要用。
6.如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
7.jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
8.泛型的指定中不能使用基本数据类型,,可以使用包装类替换
9.在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型
10.异常类不能是泛型的(因为Throwable类就不带泛型,Exception继承了Throwable,也不带泛型)
11.不能使用E[] elements = new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码Object[] elementData return (E) elementData[index];,而不是泛型参数类型数组。
12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
> 没有类型 擦除(即使用Object)
> 具体类型
子类保留父类的泛型:泛型子类
> 全部保留
> 部分保留
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型

泛型类、接口

在类/接口上声明的泛型,在本类或本接口中即代表某种类型,

可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。

在静态方法中不能使用类的泛型。原因:类的泛型在类实例化才指定,而静态方法是类模板的方法,在实例化之前执行

泛型类示例:

class Point{       // 此处可以随便写标识符号,T是type的简称  
    private T var ; // var的类型由T指定,即:由外部指定 
}
class Notepad{       // 此处指定了两个泛型类型  
    private K key ;     // 此变量的类型由外部决定  
    private V value ;
}
public class Order {

    String orderName;
    int orderId;

    //类的内部结构就可以使用类的泛型

    T orderT;

    public Order(){
        //编译不通过
//        T[] arr = new T[10];
        //编译通过
        T[] arr = (T[]) new Object[10];
    }

    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 +
                '}';
    }
    //静态方法中不能使用类的泛型。
//    public static void show(T orderT){
//        System.out.println(orderT);
//    }

    public void show(){
        //编译不通过
//        try{
//
//
//        }catch(T t){
//
//        }

    }
}

泛型方法

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

image-20220111063632799

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

public static   List copyFromArrayToList(E[] arr){
    ArrayList list = new ArrayList<>();
    for(E e : arr){
        list.add(e);
    }
    return list;
}
@Test
public void test1(){
    //如果定义了泛型类,实例化没指明类的泛型,则认为此泛型类型为Object类型
    //要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
    Order order = new Order();
    order.setOrderT(123);
    order.setOrderT("ABC");

    //建议:实例化时指明类的泛型
    Order order1 = new Order("orderAA",1001,"order:AA");

    order1.setOrderT("AA:hello");

}

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

    SubOrder1 sub2 = new SubOrder1<>();
    sub2.setOrderT("order2...");
}

@Test
public void test3(){

    ArrayList list1 = null;
    ArrayList list2 = new ArrayList();
    //泛型不同的引用不能相互赋值。
    //        list1 = list2;

    Person p1 = null;
    Person p2 = null;
    p1 = p2;


}

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

    System.out.println(list);
}

5.泛型在继承上的体现

虽然类A是类B的父类,但是G 和G二者不具备子父类关系,二者是并列关系。

补充:类A是类B的父类,A 是 B 的父类

代码示例

@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 list1 = null;
    List list2 = new ArrayList();
    //此时的list1和list2的类型不具子父类关系
    //编译不通过
    //        list1 = list2;
    /*
        反证法:
        假设list1 = list2;
           list1.add(123);导致混入非String的数据。出错。

         */

    show(list1);
    show1(list2);
}

6.通配符的使用

1.通配符:?

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

推演说明

Ingeter是Number的一个子类,由上面1.3可知:Generic与Generic实际上是相同的一种数据类型。那么问题来了,在使用Generic作为形参的方法中,能否使用Generic的实例传入呢?

在逻辑上类似于Generic和Generic是否可以看成具有父子关系的泛型类型呢?

public void showKeyValue1(Generic obj){
    log.info("泛型测试","key value is " + obj.getKey());
}

result

Generic gInteger = new Generic(123);
Generic gNumber = new Generic(456);

showKeyValue(gNumber);//正常运行

showKeyValue(gInteger); //报错:cannot be applied to Generic

结论

同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的

如何解决上面的问题?总不能为了定义一个新的方法来处理Generic类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic和Generic父类的引用类型。由此类型通配符应运而生

通配符的由来

如何解决上面的问题?总不能为了定义一个新的方法来处理Generic类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic和Generic父类的引用类型。由此类型通配符应运而生。

上面代码可改为:

public void showKeyValue1(Generic<?> obj){
    log.info("泛型测试","key value is " + obj.getKey());
}

使用 "?" 代替具体的类型实参,注意:此处’?’是类型实参,而不是类型形参

此处的 "?" 和Number、String、Integer一样都是一种实际的类型,可以把 "?" 看成所有类型的父类,是一种真实的类型。

2.涉及通配符的集合的数据的写入和读取:

@Test
public void test3(){
    List list1 = null;
    List list2 = null;

    List<?> list = null;

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


    //
    List 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);


}

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


3.泛型上下边界

通常情况下,我们不会直接只使用泛型通配符,而是使用泛型通配符再结合superextends来规定泛型的上下限。

泛型上限

  • 格式类型名称 <? extends 类 > 对象名称
  • 意义只能接收该类型及其子类

泛型下限

  • 格式类型名称 <? super 类 > 对象名称
  • 意义只能接收该类型及其父类

举例:

<? extends Number> --------》(无穷小 Number]

只允许泛型为Number及Number子类的引用调用

**<? super Number> ** --------》[Number , 无穷大)

允许泛型为Number及Number父类的引用调用

<? extends Comparable>

只允许泛型为实现Comparable接口的实现类的引用调用

@Test
public void test4(){

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

    List list3 = new ArrayList();
    List list4 = new ArrayList();
    List list5 = new ArrayList();

    list1 = list3;
    list1 = list4;
    //        list1 = list5;

    //        list2 = list3;
    list2 = list4;
    list2 = list5;

    //读取数据:
    list1 = list3;
    Person p = list1.get(0);
    //编译不通过
    //Student s = list1.get(0);

    list2 = list4;
    Object obj = list2.get(0);
    ////编译不通过
    //        Person obj = list2.get(0);

    //写入数据:
    //编译不通过
    //        list1.add(new Student());

    //编译通过
    list2.add(new Person());
    list2.add(new Student());

}	 

7.泛型嵌套

示例代码:

public static void main(String[] args) {
    HashMap> map = new HashMap>();
    ArrayList list = new ArrayList();
    list.add(new Citizen("刘恺威"));
    list.add(new Citizen("杨幂"));
    list.add(new Citizen("小糯米"));
    map.put("刘恺威", list);
    Set>> entrySet = map.entrySet();
    Iterator>> iterator = entrySet.iterator();
    while (iterator.hasNext()) {
        Entry> entry = iterator.next();
        String key = entry.getKey();
        ArrayList value = entry.getValue();
        System.out.println("户主:" + key);
        System.out.println("家庭成员:" + value);
    }
}

相关