Files
JavaYouth/docs/Java/Basis/keyAndDifficultPoints/Generic/泛型.md
2020-11-01 10:45:31 +08:00

57 KiB
Raw Blame History

title, tags, categories, keywords, description, cover, top_img, abbrlink, date
title tags categories keywords description cover top_img abbrlink date
万字长文详解Java泛型
Java
基础
泛型
Java基础
重难点
Java基础泛型 万字长文详解Java泛型。 https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_Basis/logo.png https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg 1c342bc4 2020-10-19 22:21:58

简介

泛型的优点

1、泛型的本质是为了参数化类型也就是在在不创建新的类型的情况下通过泛型指定的不同类型来控制形参具体限制的类型很明显这种方法提高了代码的复用性。

2、泛型的引入提高了安全性泛型提供了编译时类型安全检测机制该机制允许开发者在编译时检测到非法的类型。。

3、在没有泛型的情况的下通过对类型 Object 的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。

那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。

public class GlmapperGeneric<T> {
		private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
  
    public static void main(String[] args) {
        // do nothing
    }

  /**
    * 不指定类型
    */
  public void noSpecifyType(){
    GlmapperGeneric glmapperGeneric = new GlmapperGeneric();
    glmapperGeneric.set("test");
    // 需要强制类型转换
    String test = (String) glmapperGeneric.get();
    System.out.println(test);
  }

  /**
    * 指定类型
    */
  public void specifyType(){
    GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric();
    glmapperGeneric.set("test");
    // 不需要强制类型转换
    String test = glmapperGeneric.get();
    System.out.println(test);
  }
}

为什么提高了安全性?

再举例子说明一下

不安全举例

package keyAndDifficultPoints.Generic;


import java.util.ArrayList;
import java.util.List;

/**
 * @Author: youthlql-吕
 * @Date: 2020/10/15 16:09
 * <p>
 * 功能描述:
 */
public class Test_Safe {

    public static void main(String[] args) {
        test();
    }

    public static void test() {
        List arrayList = new ArrayList();
        arrayList.add("aaaa");
        arrayList.add(100);

        for (int i = 0; i < arrayList.size(); i++) {
            String s = (String) arrayList.get(i);
            System.out.println(s);

        }
    }
}    

结果:

aaaa
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at keyAndDifficultPoints.Generic.Test_Safe.test(Test_Safe.java:25)
	at keyAndDifficultPoints.Generic.Test_Safe.main(Test_Safe.java:16)

很明显的一个类型转换错误。ArrayList可以存放任意类型例子中添加了一个String类型添加了一个Integer类型再使用时都以String的方式使用因此程序崩溃了。为了解决类似这样的问题在编译阶段就可以解决泛型应运而生。

泛型提高安全性

将上面的代码稍微改一下

 public static void test01(){
        List<String> arrayList = new ArrayList<>();
        arrayList.add("aaaa");
        //下面代码编译时就直接报错了
        arrayList.add(100);

        for (int i = 0; i < arrayList.size(); i++) {
            String s = (String) arrayList.get(i);
            System.out.println(s);

        }
    }

通过泛型来提前检测类型,编译时就通不过。

泛型为什么很重要

我们看一下比较常用的JUC包

public <U> CompletableFuture<U> thenComposeAsync(
        Function<? super T, ? extends CompletionStage<U>> fn) {
        return uniComposeStage(asyncPool, fn);
    }

    public <U> CompletableFuture<U> thenComposeAsync(
        Function<? super T, ? extends CompletionStage<U>> fn,
        Executor executor) {
        return uniComposeStage(screenExecutor(executor), fn);
    }

    public CompletableFuture<T> whenComplete(
        BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(null, action);
    }

    public CompletableFuture<T> whenCompleteAsync(
        BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(asyncPool, action);
    }

这些都大量的用到了泛型,如果不把泛型学好,想真正深入源码了解一些东西,可能就完全看不懂了。

泛型类

泛型类型用于类的定义中被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类List、Set、Map。

最普通的泛型类:

package keyAndDifficultPoints.Generic;

/**
 * @Author: youthlql-吕
 * @Date: 2020/10/15 16:38
 * <p>
 * 功能描述:
 */
public class Test_GenericClass {
    public static void main(String[] args) {
        test();
    }

    public static void test(){
        /**
         * 1、泛型的类型参数只能是类类型包括自定义类不能是简单数据类型比如int,long这些
         * 2、传入的实参类型需与泛型的类型参数类型相同即为这里的Integer。
         * 3、new 后面的泛型参数可以省略
         */
        Generic<Integer> genericInteger1 = new Generic<Integer>(123);
        Generic<Integer> genericInteger = new Generic<>(123);

        Generic<String> genericString = new Generic<String>("my");

        System.out.println(genericInteger.getVar());
        System.out.println(genericString.getVar());
    }


}

/**
 * 1、此处T虽然可以随便写为任意标识常见的如T、E、K、V等形式的参数常用于表示泛型。
 * 但是为了代码的可读性一般来说:
 * K,V用来表示键值对
 * E是Element的缩写常用来遍历时表示
 * T就是Type的缩写常用在普通泛型类上
 * 2、还有一些不常见的U,R啥的
 */
class Generic<T> {
    //key这个成员变量的类型为T,T的类型由外部指定
    private T var;

    public Generic(T var) { //泛型构造方法形参key的类型也为TT的类型由外部指定
        this.var = var;
    }

    public T getVar() { //泛型方法getKey的返回值类型为TT的类型由外部指定
        return var;
    }
}

class MyMap<K, V> {       // 此处指定了两个泛型类型
    private K key;     // 此变量的类型由外部决定
    private V value;   // 此变量的类型由外部决定

    public K getKey() {
        return this.key;
    }

    public V getValue() {
        return this.value;
    }

    public void setKey(K key) {
        this.key = key;
    }

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

结果:

123
my

Process finished with exit code 0
  • 定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。

还是以上面的泛型类为例进行测试

 public static void test01() {
        Generic generic = new Generic("我是字符串");
        Generic generic1 = new Generic(123);
        Generic generic2 = new Generic(123.123);
        Generic generic3 = new Generic(false);

        System.out.println(generic.getVar());
        System.out.println(generic1.getVar());
        System.out.println(generic2.getVar());
        System.out.println(generic3.getVar());
    }

结果:

我是字符串
123
123.123
false

Process finished with exit code 0

没有报错,正确输出了。

泛型接口

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

interface Info<T>{        // 在接口上定义泛型  
    public T getVar() ; // 定义方法,方法的返回值就是泛型类型  
} 

当实现泛型接口的类,未传入泛型实参时:

/**
 * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
 * 即class InfoImpl<T> implements Info<T>
 * 如果不声明泛型class InfoImpl implements Info<T>,编译器会报错:"Unknown class"
 */
class InfoImpl<T> implements Info<T> {   // 定义泛型接口的子类
    private T var;

    public InfoImpl(T var) {
        this.setVar(var);
    }

    public void setVar(T var) {
        this.var = var;
    }

    public T getVar() {
        return this.var;
    }
}

当实现泛型接口的类,传入泛型实参时:

/**
 * 传入泛型实参时:
 * 定义一个是先烈实现这个接口,虽然我们只创建了一个泛型接口Info<T>
 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 * 即InfoImpl01<T>public String getVar();中的的T都要替换成传入的String类型。
 */
class InfoImpl01 implements Info<String> {   // 定义泛型接口的子类
    private String var;

    public InfoImpl01(String var) {
        this.setVar(var);
    }

    public void setVar(String var) {
        this.var = var;
    }

    public String getVar() {
        return this.var;
    }
}

泛型方法

在java中泛型类和接口的定义非常简单但是泛型方法就比较复杂了。

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。

最简单的一个泛型方法

public class Test_GenericMethod {

    public static void main(String[] args) {
        Test_GenericMethod test_genericMethod = new Test_GenericMethod();
        Integer integer = test_genericMethod.genericMethod(12);
        System.out.println(integer);
    }

    /**
     * 说明:
     * 1、public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
     * 2、只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
     * 3、<T>表明该方法将使用泛型类型T此时才可以在方法中使用泛型类型T。
     * 4、<T> 后面的这个T代表这个方法的返回值类型
     * 4、与泛型类的定义一样此处T可以随便写为任意标识常见的如T、E、K、V等形式的参数常用于表示泛型。
     */
    public <T> T genericMethod(T a) {

        return a;
    }
}

基本用法(非泛型类中的泛型方法)

下面来细说一下泛型方法

首先说一个误区

class Generic01<T> {
    private T key;

    public Generic01(T key) {
        this.key = key;
    }


    /**
     * 1、这个虽然在方法中使用了泛型但这并不是一个泛型方法。这只是类中一个普通的
     * 成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。所以在这个方法中才
     * 可以继续使用 T 这个泛型。
     */
    public T getKey() {
        return key;
    }

    /**
     * 1、这个方法显然是有问题的在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
     * 因为在类的声明中并未声明泛型E所以在使用E做形参和返回值类型时编译器会无法识别。
     */
//    public E setKey(E key) {
//        this.key = key;
//    }

}

基本用法(非)

package keyAndDifficultPoints.Generic;


/**
 * @Author: youthlql-吕
 * @Date: 2020/10/15 17:46
 * <p>
 * 功能描述:
 */
public class Test_GenericMethod {

    public static void main(String[] args) {
        Test_GenericMethod test_genericMethod = new Test_GenericMethod();
        Generic01<Integer> generic01 = new Generic01<>(123);

        Generic01<String> generic02 = new Generic01<>("AAAAA");

        test_genericMethod.genericMethod_test01(generic01);
        test_genericMethod.genericMethod_test02(generic02, "我是T");

        test_genericMethod.Method01(generic01);
    }

    /**
     * 说明:
     * 1、public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
     * 2、只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
     * 3、<T>表明该方法将使用泛型类型T此时才可以在方法中使用泛型类型T。
     * 4、<T> 后面的这个T代表这个方法的返回值类型
     * 4、与泛型类的定义一样此处T可以随便写为任意标识常见的如T、E、K、V等形式的参数常用于表示泛型。
     */
    public <T> T genericMethod(T a) {

        return a;
    }


    /**
     * 1、这才是一个真正的泛型方法。
     * 2、首先在public与返回值之间的<T>必不可少这表明这是一个泛型方法并且声明了一个泛型T。
     * 3、这个T可以出现在这个泛型方法的任意位置.泛型的数量也可以为任意多个
     */
    public <T> T genericMethod_test01(Generic01<T> generic01) {
        System.out.println("我是genericMethod_test01" + generic01.getKey());
        T test = generic01.getKey();
        return test;
    }

    public <T, V> T genericMethod_test02(Generic01<T> generic01, V value) {
        System.out.println("我是genericMethod_test02" + generic01.getKey() + "==> value" + value);

        T test = generic01.getKey();
        return test;
    }


    //这也不是一个泛型方法这就是一个普通的方法只是使用了Generic<Number>这个泛型类做形参而已。
    public void Method01(Generic01<? extends Number> generic01) {
        System.out.println(generic01.getKey());
    }


    //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
    //同时这也印证了泛型通配符章节所描述的,?是一种类型实参可以看做为Number等所有类的父类
    public void Method02(Generic01<?> generic01) {
        System.out.println(generic01.getKey());
    }

    /**
     * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
     * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
     * 但是只声明了泛型类型T并未声明泛型类型E因此编译器并不知道该如何处理E这个类型。
     */
//    public <T> T showKeyName(Generic01<E> generic01, T t) {
//        return t;
//    }

}

结果:

我是genericMethod_test01123
我是genericMethod_test02AAAAA==> value我是T
123

Process finished with exit code 0

泛型类中的泛型方法

当然这并不是泛型方法的全部,泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下。

package keyAndDifficultPoints.Generic;

/**
 * @Author: youthlql-吕
 * @Date: 2020/10/15 20:14
 * <p>
 * 功能描述:
 */
public class Test_GenericMethod01 {
    public static void main(String[] args) {
        Apple apple = new Apple();
        Person person = new Person();

        GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
        //apple是Fruit的子类所以这里可以
        generateTest.show_1(apple);
        //编译器会报错因为泛型类型实参指定的是Fruit而传入的实参类是Person
        //generateTest.show_1(person);

        //使用这两个方法都可以成功
        generateTest.show_2(apple);
        generateTest.show_2(person);

        //使用这两个方法也都可以成功
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}

abstract class GenericFruit {

}

class Fruit {
    @Override
    public String toString() {
        return "fruit";
    }
}

class Apple extends Fruit {
    @Override
    public String toString() {
        return "apple";
    }
}

class Person {
    @Override
    public String toString() {
        return "Person";
    }
}

class GenerateTest<T> {
    
    public void show_1(T t) {
        System.out.println(t.toString());
    }


    /**
     * 1、在泛型类中声明了一个泛型方法使用泛型E这种泛型E可以为任意类型。可以类型与T相同也可以不同。
     * 2、由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
     */
    public <E> void show_3(E t) {
        System.out.println(t.toString());
    }


    /**
     * 1、在泛型类中声明了一个泛型方法使用泛型T注意这个T是一种全新的类型可以与泛型类中声明的T
     * 不是同一种类型。也就是说main函数中使用的时候也可以是不一样的泛型类型
     */
    public <T> void show_2(T t) {
        System.out.println(t.toString());
    }
}

结果:

apple
apple
Person
apple
Person

Process finished with exit code 0

泛型方法与可变参数

再看一个泛型方法和可变参数的例子:

public class Test_GenericMethod02 {
    public static void main(String[] args) {
        print("123",753,123.12);
    }


    //必须是三个点
    public static <T> void print(T... args) {
        for (T t : args) {
            System.out.println(t);
        }
    }
}

结果:

123
753
123.12

Process finished with exit code 0

静态方法与泛型

静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

public class StaticGenerator<T> {
    
    /**
     * 1、如果在类中定义使用泛型的静态方法需要添加额外的泛型声明将这个方法定义成泛型方法
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如public static void show(T t){..},此时编译器会提示错误信息:
          "StaticGenerator cannot be refrenced from static context"
     * 2、泛型方法在方法中出现了泛型的结构泛型参数与类的泛型参数没有任何关系。换句话说
     * 泛型方法所属的类是不是泛型类都没有关系。
     * 3、泛型方法可以声明为静态的。原因泛型参数是在调用方法时确定的。并非在初始化类时确定,所以无所谓
     */
    public static <E>  List<E> copyFromArrayToList(E[] arr){

        ArrayList<E> list = new ArrayList<>();

        for(E e : arr){
            list.add(e);
        }
        return list;

    }
}

细枝末节

可能合上面的有一些重复

1、泛型异常类

//异常类不能声明为泛型类,编译报错
class MyException<T> extends Exception{
}

2、

package keyAndDifficultPoints.Generic.Minutiae;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: youthlql-吕
 * @Date: 2020/10/15 22:28
 * <p>
 * 功能描述:
 */
public class Test_Minutiae1 {
}
class Order<T> {

    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){
//
//        }

    }


    /**
     * 2、泛型方法在方法中出现了泛型的结构泛型参数与类的泛型参数没有任何关系。换句话说
     * 泛型方法所属的类是不是泛型类都没有关系。
     * 3、泛型方法可以声明为静态的。原因泛型参数是在调用方法时确定的。并非在初始化类时确定,所以无所谓
     */
    public static <E> List<E> copyFromArrayToList(E[] arr){

        ArrayList<E> list = new ArrayList<>();

        for(E e : arr){
            list.add(e);
        }
        return list;

    }
}
class SubOrder extends Order<Integer> {//SubOrder:不是泛型类


    public static <E> List<E> copyFromArrayToList(E[] arr) {

        ArrayList<E> list = new ArrayList<>();

        for (E e : arr) {
            list.add(e);
        }
        return list;

    }

}

class SubOrder1<T> extends Order<T> {//SubOrder1<T>:仍然是泛型类
}

泛型数组

package keyAndDifficultPoints.Generic;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: youthlql-吕
 * @Date: 2020/10/15 12:10
 * <p>
 * 功能描述: 测试泛型数组
 */
public class Test_GenericArray {

    public static void main(String[] args) {
        test02();
    }

    public static void test() {
        //编译错误
//        List<String>[] ls = new ArrayList<String>[10];
    }


    public static void test01() {
        //这样声明是正确的
        List<?>[] ls = new ArrayList<?>[10];
        ls[1] = new ArrayList<String>();

        //这样写编译就报错了
//        ls[1].add(1);

    }

    /**
     * 下面是sun官方文档里写的。其实不用太纠结平时泛型虽然用的多但也不会用的这么奇葩。
     */
    public static void test02(){
        List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.
        Object o = lsa;
        Object[] oa = (Object[]) o;
        List<Integer> li = new ArrayList<Integer>();
        li.add(new Integer(3));
        oa[1] = li; // Correct.
        Integer i = (Integer) lsa[1].get(0); // OK
        System.out.println(i);
    }

    //正确
    public static void test03() {
        List<String>[] ls = new ArrayList[10];
        ls[0] = new ArrayList<String>();
        ls[1] = new ArrayList<String>();

        ls[0].add("x");

    }

}

sun文档

泛型在继承方面的细节

直接看代码注释

 /*
    1. 泛型在继承方面的体现

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

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

     */
    @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;
        /*
        反证法:
        假设list1 = list2;
           list1.add(123);导致混入非String的数据。出错。

         */

    }


    @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<>();

    }

泛型通配符

我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 TEKV 等等,下面来详细讲一下这些通配符。

常用的通配符

本质上都是通配符没啥区别,只不过是编码时的一种约定俗成的东西(可以说提高了代码可读性)。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个大小写字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T 在可读性上可能会弱一些。通常情况下TEKV 是这样约定的:

  • 表示不确定的 java 类型
  • T (Type) 表示具体的一个java类型
  • K V (Key Value) 分别代表java键值中的Key Value
  • E (element) 代表Element

比较难的就是通配符,下面就着重讲一下

' ? '无界通配符

基本用法

List<Animal> listAnimals

但是如果用通配符的话:

List<? extends Animal> listAnimals

为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。

package keyAndDifficultPoints.Generic;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: youthlql-吕
 * @Date: 2020/10/15 21:25
 * <p>
 * 功能描述: 泛型通配符测试
 */
public class Test_Wildcard_Character {

    public static void main(String[] args) {
        List<Dog> dogList = new ArrayList<>();
        test(dogList);
        test1(dogList);
    }

    static void test(List<? extends Animal> animals) {
        System.out.println("test输出:");
        for (Animal animal : animals) {
            System.out.print(animal.toString() + "-");
        }
    }

    static void test1(List<Animal> animals) {
        System.out.println("test1输出:");
        for (Animal animal : animals) {
            System.out.print(animal.toString() + "-");
        }
    }


}

class Animal {
    @Override
    public String toString() {
        return "Animal";
    }
}

class Dog extends Animal {
    @Override
    public String toString() {
        return "Dog";
    }
}

test1()在编译时就会飘红

所以,对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 ),表示可以持有任何类型。像 test()方法中,限定了上界,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错,而test1()就不行。

' ? '通配符的继承

    /*
    2. 通配符的使用
       通配符:?

       类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);


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


    }

extends和super上下界

上界通配符 < ? extends E>

上结:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功
  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用

下界通配符 < ? super E>

下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object

在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

举例

 /*
    3.有限制条件的通配符的使用。
        ? extends A:
                G<? extends A> 可以作为G<A>和G<B>的父类其中B是A的子类

        ? super A:
                G<? super A> 可以作为G<A>和G<B>的父类其中B是A的父类

     */
    @Test
    public void test4() {

        List<? extends Person> list1 = null; //[-无穷,Person]
        List<? super Person> list2 = null;  //[Person,+无穷]

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

        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());

    }
}

class Person {
}

class Student extends Person {
}

## 和 T 的区别

?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 不行,比如如下这种

// 可以
T t = operate();

// 不可以
 car = operate();

简单总结下:

T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

区别1通过T来确保泛型参数的一致性

package keyAndDifficultPoints.Wildcard_Character;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: youthlql-吕
 * @Date: 2020/10/16 11:28
 * <p>
 * 功能描述:
 */
public class Test_difference {

    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        List<Float> floatList = new ArrayList<>();

        //编译报错
//        test(integerList, floatList);
        //编译通过
        test1(integerList, floatList);


        //编译通过
        test(integerList, integerList);
        test1(integerList, integerList);

    }



    // 通过 T 来 确保 泛型参数的一致性
    public static <T extends Number> void test(List<T> dest, List<T> src){

    }

    //通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
    public static void test1(List<? extends Number> dest, List<? extends Number> src){

    }
}

区别2T可以通过&进行多重限定

public class Test_difference {

    public static void main(String[] args) {


        /*---------------------测试多重限定符---------------------*/
        ArrayList list = new ArrayList<>();
        ArrayDeque deque = new ArrayDeque<>();
        LinkedList<Object> linkedList = new LinkedList<>();

        //多重限定时,在编译的时候取最小范围或共同子类

        test2(list);
//        test3(list); 编译报错

        //编译报错
//        test2(deque);
//        test3(deque);

        //编译通过
        test2(linkedList);
        test3(linkedList);


    }


    //可以进行多重限定
    public static <T extends List & Collection> void test2(T t) {

    }

    //可以进行多重限定
    public static <T extends Queue & List> void test3(T t) {

    }

    //编译报错,无法进行多重限定
//    public static <? extends List & Collection> void test4(List<T> dest, List<T> src){
//
//    }

}

区别3通配符可以使用超类限定而T不行

类型参数 T 只具有 一种 类型限定方式:

T extends A

但是通配符 ? 可以进行 两种限定:

? extends A
? super A

关于反射和泛型的一点东西

package keyAndDifficultPoints.Wildcard_Character;

/**
 * @Author: youthlql-吕
 * @Date: 2020/10/16 12:09
 * <p>
 * 功能描述: 泛型反射
 */
public class Test_Reflect {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        A a = createInstance(A.class);
        B b = createInstance(B.class);
    }

    /**
     * 这样写明显是要安全很多的
     */
    public static <T> T createInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException {
        return clazz.newInstance();
    }

    public static void getA(String path) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        A a = (A) Class.forName("keyAndDifficultPoints.Wildcard_Character.A").newInstance();
        //很明显下面的这行代码是错的但是写代码的时候你不知道path是哪个
//        B b = (B)Class.forName("keyAndDifficultPoints.Wildcard_Character.A").newInstance();
        System.out.println(a.toString());
    }
}

class A {
    String name;

    @Override
    public String toString() {
        return "我是对象A";
    }
}

class B {
    String name;

    @Override
    public String toString() {
        return "我是对象B";
    }
}


class C {
    //所以当不知道声明什么类型的 Class 的时候可以定义一 个Class<?>。
    public Class<?> clazz1;

    //因为T没有声明所以编译报错
//    public Class<T> clazz2;
}

class D<T> {
    public Class<?> clazz;
    // 不会报错
    public Class<T> clazzT;
}

泛型原理(泛型擦除)

类型擦除简介

Java的泛型是伪泛型为什么说Java的泛型是伪泛型呢因为在编译期间所有的泛型信息都会被擦除掉我们常称为泛型擦除

Java中的泛型基本上都是在编译器这个层次来实现的在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数编译器在编译的时候去掉这个过程就称为类型擦除。

如在代码中定义的List