面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:

目录

what's Generic?

泛型的概念:

泛型的好处:

泛型类、泛型接口、泛型方法中常用的泛型标识符的意义释义:

常用的泛型标识符说明:

自定义泛型类(常用):

自定义泛型接口(常用):

自定义泛型类、泛型接口的继承与实现:

自定义泛型方法(常用):

泛型数组以及泛型对象:

创建泛型数组 方式一:

 创建泛型数组 方式2(这种方式 安全一些 ):

泛型对象:

泛型的使用细节以及注意事项(重点):

自定义泛型类及自定义接口的使用细节以及注意事项:

自定义泛型方法的使用细节以及注意事项:

泛型数组的使用细节以及注意事项:

泛型数组的使用细节:

                      5. 泛型数组的注意事项:

泛型对象注意事项:

泛型的通配符(常用):

泛型的上下限(常用):

泛型的上限通配:

泛型的下限通配:

关于泛型的通配符、上下限的细节说明:

泛型的通配符、上下限的使用场景:

泛型的 泛型擦除(编译时泛型擦除、运行时泛型擦除):

泛型的编译时擦除:

泛型的运行时擦除:

泛型擦除 的无限制 类型擦除:

泛型擦除 的有限制 类型擦除(即使用了 泛型的上限通配 )

泛型方法的 泛型擦除 :

泛型擦除前的桥接方法:

往 泛型指定为Double类型的List集合中添加非 Double 的实例:


what's Generic?

  • 泛型(广泛的类型):
    • 泛型就相当于是一个标签。形象一些就是:在生活中随处可见的类似于泛型的参考。比如:男卫生间门上贴的标签  --》 男 or boy ,女卫生间门上的标签 --》 女 or girl,能表示性别的标签 理解在Java中就叫做泛型。再 比如:垃圾分类的垃圾桶上面贴的标签,比如:厨余辣鸡、有害辣鸡、可回收辣鸡、其他辣鸡,……等等。
  • 泛型的声明格式:<泛型标识符[,泛型标识符2,……]>   这个 <>  因为像,也叫做钻石运算符。
  • Java中 泛型的支持:泛型只支持引用数据类型,不支持基本数据类型!!!!
  • 泛型的概念:

    • 集合(有的叫容器)类在设计阶段/声明阶段,并不能够确定这个容器 实际应该存入什么样类型的对象。所以在 JDK1.5 之前都只能把元素的类型设计为Object。这样在使用集合的时候。如果想使用添加的元素的类型的特有成员时。就难免会使用到 类 的向下转型!所以很有可能引发 ClassCastException 异常!
    • JDK1.5 开始 就使用泛型来解决。因为这个时候除了集合中的元素类型不确定。其他的部分都是确定的。比如关于这个元素应该怎么存储。怎么进行管理这些逻辑是 集合中的API 已经实现了逻辑了的。所以这时把元素的类型 设计成为一个参数(泛型标识符)的话。这个类型 就叫做泛型!在实例化集合类的时候。当开发者指定为 什么 数据类型,那么集合中所有使用到 泛型标识符 的地方都会在 编译时成为 开发者指定的数据类型!从而就可以直接在实例化的地方调用 该类型特有和继承、实现下来的成员!
    • 面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第1张图片
    • ·Java中泛型(Generic)是在 JDK1.5 引入的新特性。泛型的主要作用就是提供了 编译时类型 的安全监测机制。该机制允许开发者在编译时 检测到非法的类型数据结构,即指定了什么数据类型,就只能操作什么数据类型的成员!!
      • 泛型的本质就是参数化类型。换言之,就是所操作的数据类型被指定为一个参数!当使用泛型类的时候,开发者给定了什么类型,那么泛型类 的 泛型标识符所表示的数据类型就是 该类型!从而就可以直接使用、 该类型特有和继承、实现下来的成员!
  • 泛型的好处:

    • Java泛型可以在编译阶段 保证  数据的安全性。比如在集合类中的体现。 当泛型指定为 List list = new ArrayList<>(); 当调用 list.add() API 的时候,就只能往集合中添加String类型的 实例。
    • 消除了 类 的向下转型 。消除了 有可能引发的 ClassCastException 异常的产生!同时,代码更加简洁、健壮。
    • 在Java的源码中、各种设计模式、框架中 泛型得到广泛的应用!

泛型类、泛型接口、泛型方法中常用的泛型标识符的意义释义:


自定义泛型类(常用):

例如:

class Son {
    private T t;
    private T[] tArr;
    public final List list = new ArrayList<>();

    public void setT(T t) {
        this.t = t;
    }

    public T getT() {
        return t;
    }
    
    public List getList(){
        return list;
    }
}

test:

  1. 面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第2张图片,此时会看到在编译阶段(也就是写代码时),set方法的数据类型就行在 写泛型类的时候 开发者指定的数据类型。达到了数据类型的安全性!
  2. 面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第3张图片,此时会发现,在getT返回设置的属性的时候,就已经是开发者给定的数据类型。从而就可以直接调用String类中的API,就不用再进行类型的强制转换!
  3. 面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第4张图片,而get到 List集合的时候,此时的 add方法中的入参类型已经变成了 在一开始实例化对象的时候。就已经给定了的 数据类型!
  4. 另外在集合中的表现是一回事,比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第5张图片面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第6张图片面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第7张图片

自定义泛型接口(常用):

例如:

interface Person{
    S s();
    Map setMap(U u,Q q);

    default void print(T t,S s){
        System.out.println(t.getClass());
        System.out.println(s.getClass());
    }
}

test:和自定义泛型类是一样的使用,只是接口的泛型是 它的实现类在初始化时、定义时 或者是 子接口定义时、或者是 子抽象类定义时指定的!

 


自定义泛型类、泛型接口的继承与实现:

形式1(稍微复杂些的)(不常用):

interface Person {
    public abstract void personMethod(Q q);
}
// 不给定父接口泛型
interface SubPerson extends Person {
    default void subMethod(C c) {
        System.out.println(c.getClass());
    }
}
// 给定父接口泛型,同时声明自己类中的泛型
abstract class AbstractPerson implements SubPerson, HashMap> {

    @Override
    public void personMethod(List list) {
        list.add("抽象类向集合中添加元素");
    }

    public abstract ArrayList personMethod(T t, U u);

    protected void method2(S s) {
        System.out.println(s.getClass());
    }
}
// 给定父接口的第三个泛型为 Scanner 类型
// 其他两个泛型在实例化 Coder的时候再指定。
// Coder再声明一个自己的泛型。
class Coder extends AbstractPerson {

    private T t;
    private U u;
    public Coder(T t,U u){
       this.t = t;
       this.u = u;
    }
    public T getT(){
        return t;
    }
    public U getU(){
        return u;
    }
    @Override
    public ArrayList personMethod(T t, U u) {
        ArrayList list = new ArrayList<>();
        Collections.addAll(list, t, u);
        return list;
    }

    public Double useR(R r) {
        if (r instanceof Integer) {
            return Double.valueOf(calculate((Integer) r, 100));
        } else {
            System.out.println(r.getClass());
        }
        return Math.PI;
    }

    private int calculate(int start, int target) {
        return (start + target) * (target / 2);
    }
}

test:

public static void testGenericExtends() {
        Coder coder =
                new Coder("第二个泛型为String类型",Math.E);
        coder.method2(new Scanner(System.in));
        // 直接以实例化时给定的泛型为准
        String t = coder.getT();
        System.out.println(t);
        Double u = coder.getU();
        System.out.println(u);

        coder.personMethod(new ArrayList<>());
        ArrayList objects = coder.personMethod("圆周率:", Math.PI);
        System.out.println(objects);

        Double result = coder.useR(1);
        System.out.println(result);
    }

 面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第8张图片

形式2(什么泛型都不在定义泛型类、泛型接口的时候 给定)(常用)

  • 说明:
  • 如果子类、子接口、实现类什么类型都不在定义指定的话。那该类、该接口 就是成为泛型类、泛型接口。并且泛型标识符要和父类、父接口的一模一样!
  • 其实子类、子接口、实现类 可以更改父类、父接口的泛型标识符的,但是更改了之后!在定义自己类、接口的时候,也要是这个标识符!(一般不会这样做!)
  • 如果子类自己有拓展的标识符的话,那么和父类泛型标识符一样的那个标识符 在子类自己定义类 声明泛型标识符的时候,和父类泛型标识符一样的那个标识符可以放在泛型声明的 钻石运算符中的任意位置 。只是这可能会产生不好理解的情况。
  • interface People {
        public abstract Integer peopleMethod(T t);
    }
    
    interface Man extends People {
        @Override
        Integer peopleMethod(T t);
    
        default String m1(S s, T t) {
            return s.getClass().toString() + "和" + t.getClass().toString();
        }
    }
    
    abstract class Dad implements Man {
        protected T t;
    
        protected Dad(T t) {
            this.t = t;
        }
    
        @Override
        public Integer peopleMethod(T t) {
    
            return (Integer) t + 999999;
        }
    }
    
    class Child extends Dad {
        private S s;
    
        public Child(T t, S s) {
            super(t);
            this.s = s;
        }
    
        public S getS() {
            return s;
        }
    
        public List getList() {
            return new LinkedList<>();
        }
    }

    test:

    public static void main(String[] args) {
            Child child = new Child<>(9090, "Created at 2021-06-14 08:59");
            String s = child.getS();
            System.out.println(s);
            Integer integer = child.peopleMethod(789);
            System.out.println(integer);
            Integer t = child.getT();
            System.out.println(t);
    
            List list = child.getList();
            list.add(new Date());
            list.add(new java.sql.Date(System.currentTimeMillis()));
            list.add(new ArrayList().add(null));
            ArrayList currentTimeMillis = new ArrayList<>();
            currentTimeMillis.add(new GregorianCalendar().getTime().getTime());
    
            System.out.println(list);
            System.out.println(currentTimeMillis);
            System.out.println(child.m1(new String(), Integer.MAX_VALUE));
        }

    test:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第9张图片面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第10张图片


自定义泛型方法(常用):

  1. 泛型方法声明格式:方法修饰符 <泛型标识符> 返回值类型 方法名(泛型 形参名,[其他数据类型 形参名]){return 返回值;}
  2. public class GenericTest3 {
        public static void main(String[] args) {
            Generic generic = new Generic<>();
            Generic generic1 = generic.isNotGenericMethod(generic);
            System.out.println(generic);
            System.out.println(generic1);
    
            String s = generic.isGenericMethod("是String类型");
            System.out.println(s);
            ArrayList dates = new ArrayList<>();
            List> listInstance = Generic.getListInstance(dates);
            listInstance.add(dates);
            System.out.println(listInstance.getClass());
        }
    
    }
    
    class Generic {
        private T t;
    
        public Generic(){}
        // 此方法并不是泛型方法
        public T isNotGenericMethod(T t) {
            return isNotGenericMethod2(t);
        }
        // 此方法也不是泛型方法
        private T isNotGenericMethod2(T t) {
            T tt = null;
            try {
                // 通过这样的反射方式 创建本类对象,构造器必须显示初始化才可以!
              tt = (T) t.getClass().getConstructor().newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return tt;
        }
    
        // 是泛型实例方法
        public  S isGenericMethod(S s){
            if ("是String类型".equals(s)){
                return s;
            }
            return null;
        }
    
        // 是泛型静态方法
        public static  List getListInstance(U u){
            if (u instanceof List){
                return new LinkedList<>();
            }
            return null;
        }
    }
    result:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第11张图片,泛型在编译时的数据类型:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第12张图片面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第13张图片面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第14张图片

泛型数组以及泛型对象:

创建泛型数组 方式一:

class Generic2 {
    private G[] gArr;
    private Object obj;

  public  Generic2(T t) {
        // 多态的引用!
        obj = t;
        // 创建泛型数组的方式1:
        // (如果在实例化Generic2的时候,给定的泛型不是 被强转的类型时就会
        // 引发并抛出 ClassCastException 异常,
        // 因为向下转型的前提是要
        // 父类引用指向子类对象时。
        // 这时候的向下转型到子类类型的时候 才不会抛出 ClassCastException
        // (G[]) new Object[5] 如果这里的向下转型在实例化Generic2的时候
        // 指定的泛型如果不是Object时,是其他类的时候,就会抛出 ClassCastException异常
        // 因为要清楚的知道的是 new Object[5] 这个动作是在 初始化 Object类型的
        // 数组,而不是在初始化 泛型的数组,泛型的数组不能够被初始化!
        // 因为泛型是未知的数据类型,怎么初始化?)
        gArr = (G[]) new Object[5];
    }

    public G[] getgArr(){
        return gArr;
    }
    public Object getObj(){
        return obj;
    }
}

 test1:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第15张图片使用泛型数组是,引发的ClassCastException异常情况:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第16张图片


 

 创建泛型数组 方式2(这种方式 安全一些 ):

class Generic2 {
    private G[] gArr;
    private Object obj;

    public  Generic2(T t) {
        // 多态的引用!
        obj = t;
        // 创建泛型数组的方式1:
        // (如果在实例化Generic2的时候,给定的泛型不会 被强转的类型时就会
        // 引发并抛出 ClassCastException 异常,
        // 因为向下转型的前提是要
        // 父类引用指向子类对象时。
        // 这时候的向下转型到子类类型的时候 才不会抛出 ClassCastException
        // (G[]) new Object[5] 如果这里的向下转型在实例化Generic2的时候
        // 指定的泛型如果不是Object时,是其他类的时候,
         // 如果在类的外部获取 G[] 这个泛型数组,就会抛出 ClassCastException异常
        // 因为要清楚的知道的是 new Object[5] 这个动作是在 初始化 Object类型的
        // 数组,而不是在初始化 泛型的数组,泛型的数组不能够被初始化!
        // 因为泛型是未知的数据类型,怎么初始化?)
        // 如果这里用了  (G[]) new Object[5] 其本质就是在使用 Object类型的数组!
        // 这种方法没有任何意义。要么就直接使用 Object 的数组,没有必要转来转去。
        gArr = (G[]) new Object[5];
    }


    public G[] initializeGArr(Class clazz, int initLength) {
        // 创建泛型数组的方式2:通过反射包下( java.lang.reflect.Array;)的 Array类的newInstance创建数组对象
        /* public static Object newInstance(Class componentType, int length)
        throws NegativeArraySizeException {
        return newArray(componentType, length);
            }
            这个API的本质仍是返回的Object类型。)(newArray底层是一个本地方法,由C或者C++实现)
            而引用数据类型的数组都继承自 Object 和 Object[]
            基本数据类型的一维数组只继承自 Object,它们并没有继承自 Object[]
            因为基本数据类型并不是引用数据类型!

            (需要说明的是,基本数据类型
            二维数组继承自 Object 、Object[]
            三维数组继承自 Object 、Object[] 、 Object[][]
            依次类推……

            引用数据类型
            二维数组继承自 Object、Object[] 、Object[][]
            三维数组继承自 Object 、Object[] 、 Object[][] 、Object[][][]
            依次类推……)
            */
        gArr = (G[]) Array.newInstance(clazz, initLength);
        return gArr;
// return (G[]) Array.newInstance(clazz, initLength);
    }

    public G[] getgArr() {
        return gArr;
    }

    public Object getObj() {
        return obj;
    }
}

/*
使用反射创建泛型数组的对象的时候方法签名中接收Class对象时 
可以有的形式:它们四种方式都有可能引发 ClassCastException!!! 不是绝对安全的!

第一种:使用 泛型通配符:
public G[] initializeGArr(Class clazz, int initLength)

第二种:使用 泛型下限通配:
public G[] initializeGArr(Class clazz, int initLength) 

第三种:使用 泛型上限通配:
public G[] initializeGArr(Class clazz, int initLength) 

第四种:就是不使用泛型(泛型擦除):
public G[] initializeGArr(Class clazz, int initLength) 
*/

test:

 public static void main(String[] args) {
        Generic2> generic2 = new Generic2<>(123);
        HashMap[] hashMaps = generic2.initializeGArr(HashMap.class, 3);
        HashMap map = new HashMap<>();
        map.put("first", "Array.newInstance(Class componentType, int length)通过反射创建泛型数组");
        hashMaps[0] = map;
        for (int i = 0; i < hashMaps.length; i++) {
            System.out.println(hashMaps[i]);
        }
        System.out.println("hashMaps.length = " + hashMaps.length);
        System.out.println("hashMaps.getClass() = " + hashMaps.getClass());
        Object obj = generic2.getObj();
        System.out.println("obj.getClass() = " + obj.getClass());
    }

result: 面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第17张图片

 

泛型对象:

public static void main(String[] args) {
        Generic2 Generic2 = new Generic2<>();
        Generic generic = new Generic<>();
        Generic instance = Generic2.getGInstance(generic);
        System.out.println(generic.toString());
        System.out.println(instance.toString());
        
        String str = Generic2.getInstance(String.class);
        System.out.println(str.getClass().getSuperclass().getSimpleName());

    }

    // 一把都不这样创建 类的泛型的对象。
    // 直接在外部使用的时候创建就好了,
    // 为什么在泛型类中创建?
    public G getGInstance(G g) {
        return reflectNewInstance(g);
    }

    private G reflectNewInstance(G g) {
        try {
            g = (G) g.getClass().getConstructor().newInstance();
        } catch (Exception e) {

        }
        return g;
    }

    // 可以使用泛型方法创建其他类的对象!
    /* 前提是这个类本身就要能够创建对象*/
    public  T getInstance(Class c) {
        T t = null;
        try {
            t = c.getConstructor().newInstance();
        } catch (Exception e) {

        }
        return t;
    }

    public Generic2() {
    }

result:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第18张图片


泛型的使用细节以及注意事项(重点):

  • 自定义泛型类及自定义接口的使用细节以及注意事项:

    1. 不管是使用 自定义的泛型类还是用别人写的泛型类。在实例化泛型类的时候,在有 数据类型 对象名 引用的情况下。都不用再指定泛型。在JDK1.7之前需要手动指定。到时再JDK1.5之后,编译器会自行推断。比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第19张图片
    2. 泛型类的实例成员(实例变量、常量、方法、代码块、内部类)中可以直接使用类的泛型!
    3. 类的静态成员(静态变量、常量、方法、代码块、静态内部类、静态内部枚举、静态内部接口、静态内部注解)不能够使用 类的 泛型。因为泛型是属于类的实例的,静态方法是属于类的,并不是属于类的实例的。静态成员加载时还没有创建对象。所以不能够使用类的泛型。
    4. 泛型类在继承类的时候,这个父类不一定要是 泛型类。可以是一个普通的类。在继承、实现泛型接口的时候,这个父接口不一定要是个 泛型接口,可以是一个普通接口。
    5. 泛型类在继承泛型父类 or 实现接口、包括泛型子接口继承泛型父接口的时候,如果泛型类在定义的时候,没有给 泛型父类 or 泛型父接口给定具体数据类型的话,那么这个泛型类、泛型子接口 的 泛型声明 也要是和泛型父类、泛型父接口的泛型声明一样!
    6. 泛型类在继承泛型父类 or 实现接口的时候,泛型类在定义的时候,可以在继承 or 实现时 更改泛型父类 or  泛型父接口的 泛型标识符 ,此时泛型类的 泛型声明的泛型标识符要和自己修改的一样!(一般很少这样操作)子泛型接口 继承 父泛型接口也是一样!
    7. 泛型类在继承泛型父类 or 实现泛型接口的时候,泛型类在定义的时候,泛型类的泛型声明的泛型标识符的顺序可以和 泛型父类 or 泛型父接口的不一致。但是这样很容易搞混淆(基本上 不会把顺序搞得不一样。)子泛型接口 继承 父泛型接口也是一样!
    8. 一个普通类 也可以继承、实现 泛型父类、泛型父接口、一个 普通接口 也可以继承 泛型父接口。但是前提是 在 继承 or 实现的时候,要给 泛型父接口、泛型父类给定具体的数据类型!
    9. 泛型类在继承或者实现时,泛型接口在继承时。可以拓展自己的泛型声明!可以有 0 ~ n个泛型声明!
    10. 泛型类的构造器写法和普通类的构造器写法是一样的,泛型类的构造器后面没有<>这个符号。比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第20张图片,不会因为这个类是个泛型类,构造器的写法就有所改变。构造器如果想使用自己独有的泛型的话,那么这个构造器可以定义成为泛型构造器。
    11. 泛型类中的静态变量 以及 静态常量 不能使用 泛型!类中的静态成员有 静态方法、静态内部类、静态内部接口 可以使用自己的泛型!静态的成员不能够使用类的泛型!
    12. 如果在 定义 泛型类 的时候 没有给泛型父类和泛型父接口给定具体类型的话。那么在实例化这个 泛型类的时候,尽量给定具体的数据类型。不然就会 发生编译时的 泛型擦除。即 所有的 泛型标识符 表示的数据类型 都会泛型擦除为 Object 类型。(要么所有的泛型都不给定具体数据类型。要么全部泛型都要给定具体数据类型,没有给定几个和不给定几个的操作!)
    13. 泛型类的泛型是在 泛型类继承 泛型父类、实现 泛型接口 或者是 实例化泛型类的时候 给定具体数据类型。如果说 在 定义时和实例化时都没有给定具体的数据类型。那么这些泛型声明就啥作用都没有,全部泛型擦除为Object类型
    14. 自定义异常类不可以是泛型类,编译器不支持。但是 异常类中可以有 泛型方法。
    15. 自定义枚举类不可以是泛型类,编译器不支持。但是 枚举类中可以有 泛型方法。
    16. 自定义注解不可以是泛型注解,编译器不支持。

  • 自定义泛型方法的使用细节以及注意事项:

    1. 泛型方法可以分为 泛型静态方法 和 泛型实例方法 和 泛型构造器 。
    2. 泛型方法必须要有 <泛型标识符> 泛型声明(钻石运算符 ) !不然这个方法就不是泛型方法!如果是实例方法,那么只能说这个方法使用了类的泛型。而它本身不是泛型方法。
    3. 泛型方法   可以出现在 普通类、普通抽象类、普通接口、泛型类、泛型抽象类、泛型接口、枚举类、自定义异常类、静态内部类、局部内部类(只能有泛型实例方法)、实例内部类(只能有泛型实例方法)、匿名内部类(只能有泛型实例方法)
    4. 泛型方法的重载注意事项(不能是相同的泛型声明,这和是不是相同的泛型标识符无关,因为它们 泛型声明的 泛型擦除后的 类型都是Object,所以不构成泛型方法的重载!):面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第21张图片面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第22张图片构造泛型方法的重载就要满足方法重载的必要条件,即(方法名必须相同、参数类型不同、或者 参数类型个数不同 或者参数类型顺序不同 (和是不是静态方法、实例方法无关!)面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第23张图片泛型方法重载的坑:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第24张图片这样 重载实例方法的时候 虽然在编译时通过了编译,倒是如果在调用的时候,传入了String类型的话。这个时候,编译器就懵逼了。比如面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第25张图片引发编译不通过的错误:,解决方法:要么实例化泛型类的时候就不要指定泛型为 和已有的重载方法形参类型相同 ,要么就把 已经给定了的数据类型的那个重载方法改成其他数据类型!比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第26张图片
    5. 泛型方法的泛型是独立于类而存在的,类、接口的泛型是以类、接口为单位!而方法的方法是以方法为单位!即 即使泛型方法的泛型声明的泛型标识符和类、接口的泛型标识符是一样的。也是独立开来的,这并影响 泛型方法的调用。为了避免歧义。所以 Java规定了要 写 泛型声明 !
    6. 泛型方法的泛型声明 必须在返回值的前面、方法修饰符的后面!
    7. 泛型实例方法、泛型构造器 既可以使用方法、构造器 自己本身的泛型,也可以使用 类的泛型。而泛型静态方法只能够使用 泛型静态方法自己本身 的泛型!
    8. 泛型方法的泛型是在调用方法时 给定具体的 数据类型 的,即调用泛型方法时,传入了 什么引用数据类型的实例(即 实参)调用方法,即 这个泛型表示符 所表示的 数据类型就是 这个实参的 数据类型。
    9. 而 (泛型类泛型接口的泛型 可以是在 类、泛型类 继承 泛型父类,实现泛型父接口的时候 给泛型父类、泛型父接口 给定具体的数据类型)(一般都不会在继承、实现是给定 引用数据类型,只是说可以zheyan)。如果没有给定,那么这个泛型类的 泛型标识也要和 泛型父类、泛型父接口的泛型标识一样。如果没有在继承 or 实现的时候给定 泛型父类、泛型父接口具体的数据类型。那么在实例化 泛型类 的时候,也可以 给定 泛型的具体类型!

  • 泛型数组的使用细节以及注意事项:

    1. 创建泛型数组的方式1: 比如:T[] tArr = (T[]) new Object[5] ;  如果在 实例化泛型类的时候,给定的泛型不是 被强转的类型时 就会 引发并抛出 ClassCastException 异常。这是非常危险的操作。如果是用Object[]来接的话,会发现没报错。那是因为 new Object[5]  这个操作确实就是 初始 Object的数组!!而在运行时,泛型是被擦除了的(泛型只保留在编译阶段)!!!具体细节在上面已经在  泛型数组以及泛型对象:阐明清楚了 。
    2. 创建泛型数组的方式2:通过反射包下( java.lang.reflect.Array;)的 Array类的newInstance创建数组对象,   public static Object newInstance(Class componentType, int length){return newArray(componentType, length);}        这个API的本质仍是返回的Object类型。)(newArray底层是一个本地方法,由C或者C++实现);

    3. 关于数组的继承关系:引用数据类型的一维数组都继承自 Object 和 Object[],基本数据类型的一维数组只隐式继承自 Object,它们并没有隐式继承自 Object[],因为基本数据类型并不是引用数据类型!                      (需要说明的是,基本数据类型
                                                                                       二维数组隐式继承自 Object 、Object[]
                                                                                      三维数组隐式继承自 Object 、Object[] 、 Object[][]

                                                                                     依次类推……

                                                                                        引用数据类型
                                                                                      二维数组隐式继承自 Object、Object[] 、Object[][]
                                                                                      三维数组隐式继承自 Object 、Object[] 、 Object[][] 、Object[][][]
                                                                                     依次类推……)

    4. 泛型数组的使用细节:

      1. 如果是想操作基本数据类型的数组的话,当实例泛型类时 在 给定泛型的时候 声明为 基本数据类型的数组时,比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第27张图片

        ,但是这种方式缺点太明显了。明明初始化 给定的是char[] 因为的类型。但是在调用这个方法的时候却要传入一个 char类型的 char[][] 二维数组。那是因为 U[] 这个声明又在 char[] 这个一维数组又声明了一维,所以才变成了二维数组。因为要明确的是 只有 char[] 这个一维数组类型才是隐式 继承了 Object,所以编译通过,而 char 这个类型本身就是基本数据类型。它并没有隐式继承 Object!。所以为什么是 给定的是char[] 类型,但是 却要传入 一个 char[][] 的二维数组才能使用!所以如果想要使用 char[] 类型的 的数组的话,只能使用它的 包装类型!面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第28张图片

        ,这样使用数组的话 就不会造成明明给定的是 一维数组 但是使用的时候却要二位数组才能使用。比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第29张图片需要特别注意的是。这里的 给定具体的数据类型的时候 。只需要给定 类型 即可。不要再在后面声明数组的形式传入进去!如果在后面声明数组的话,那么在操作的时候,仍然是 要传入给定类型的 二维数组才能操作! 依次类推!!!

        比如:指定了的话。就是这么一个效果(和直接使用基本数据类型的数组一样):面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第30张图片

                      5. 泛型数组的注意事项:

  •  一般都不会在 泛型类中 创建 泛型数组。一般都是在实例化泛型类的时候才创建泛型数组 来使用。如果泛型类中 要使用到 泛型数组的 时候,可以考虑使用 List、Set、Map集合来代替。因为 在泛型类中 创建泛型数组的话,当给定泛型的数据类型,不会new 的那个类型的话。比如:T[] tarr = (T[])new  Object[10]; 这样子的话,如果实例化泛型类的时候 确切的数据类型不是 Object的话。如果在类的外部想获取 T[] 这个数组时,就会引发 ClassCastException异常。需要清楚的是 new  Object[10]; 这个动作是在初始类型 为Object类型的数组!!
  • 如果 非要 使用泛型数组 不可  的话,就 可以使用 通过反射的方式来创建泛型数组即可,并且接收 Class 类对象的方法的签名 尽量是 泛型上下限通配。如:
  • public G[] initializeGArr(Class clazz, int initLength) 
    如果是 这种  泛型通配上限 的 这种情况的话 :可以传入自己的 类对象,以及父类的类对象。比如:
    (可以拿自己的类型来接收或者拿父类的类型 多态接收)。
    如果是传入的父类的 类对象的话,就必须要 拿 父类自己本身的类型 或者 父类的父类的类型 多态接收。
    
    

     public G[] initializeGArr(Class clazz, int initLength) :
    如果是这种  泛型下限通配  的情况的话,可以传入自己的类对象,以及所有子类的类对象。
    接收类型可以是子类类型本身(强制类型转换),也可以父类类型 多态引用!
    如果是子类的类型和 其他父类类型 引用的时候,就要进行类型的 强制转换!
    因为在编译时,编译器就一直会以 给定的 数据类型 为父类,从而 一直以多态的方式 指向 子类 对象。
    而运行时泛型擦除后 是以 Object 来指向的。          
    
  • 其他方式:比如:(使用通配符)public G[] initializeGArr(Class clazz, int initLength)  或者不使用通配符  public G[] initializeGArr(Class clazz, int initLength),这样方法签名也可以,不过这样的方法签名很容易就 引发ClassCastException 异常。因为所有类的类对象都可以接收,一点 编译类型检查 都没有。!

 

泛型对象注意事项:

和泛型数组一样的。一般都不会在泛型类中 去创建一个泛型的对象。 我都不知道什么类型?怎么创建?

如果非要创建这个泛型对象不可的话,和泛型数组的注意事项是一样的,尽量通过 泛型的上下限 让泛型受限。尽量通过反射来实例化对象。应该尽量避免出现 ClassCastException 异常 和其他一些 可能发生 的异常。


泛型的通配符(常用):

  • 泛型的通配符 (就是一个问号): ?       ,未知的,无法预知的。
  • 泛型的通配符的作用 : 面向对象  多态 的体现。对一个方法进行 泛型的通配符定义,那么这个方法就可以接收任意的 相对应的子类、实现类类型。
  • 泛型的通配符的使用细节:
    • 泛型不具备继承性。比如面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第31张图片如果想让编译通过的话。那么两个集合对象的泛型的数据类型要完全一样才可以!!!面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第32张图片但是这样的意义不大。因为泛型 都是一样的数据类型的集合,为什么要引用来引用去的?直接操作各自不就好了吗?有没有一种办法就是。和 集合的泛型的 给定的 数据类型无关。来一个 均是它们的父类时。这个 对象就可以随意引用呢? 所以这里 就可以用到 泛型的 通配符了。!比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第33张图片(当前也可以在List list 后 实例化一个 实现类对象。但这没有任何的意义存在!还要初始化一遍 对象。没有必要的开销),可以看见这将会非常的方便。满足了 泛型的数据类型不同 集合需要一个类型来引用的情况。这种 便利通常会用在成员方法中 或者 泛型方法中,但是泛型通配符 声明的类型 有一个弊端就是不能往集合中添加元素(除了添加引用类型的默认值: null 以外)!比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第34张图片
    • 泛型通配符在成员方法中 or 实例方法中的体现:
      class Created {
          private T t;
      
          public Created(T t) {
              this.t = t;
          }
      
          public static void main(String[] args) {
              List oList = new LinkedList<>();
              oList.add(LocalDateTime.now(ZoneId.of("UTC+8")));
              List sList = new ArrayList<>();
              sList.add("String");
              List iList = new Vector<>();
              iList.add(2);
              List dVector = new Vector<>();
              dVector.add(3.1415);
      
              Map hashMap = new HashMap<>();
              hashMap.put("牛郎","织女");
              Map linkedHashMap = new LinkedHashMap<>();
              linkedHashMap.put("husband","wife");
              Map hashMap1  = new HashMap<>();
              hashMap1.put("boy","girl");
              Map treeMap = new TreeMap<>();
              treeMap.put("male","female");
              invokeEachList(oList, hashMap);
              invokeEachList(sList, linkedHashMap);
              invokeEachList(iList, hashMap1);
              invokeEachList(dVector, treeMap);
          }
      
          private T getT() {
              return t;
          }
      
          public static void invokeEachList(List list, Map map) {
              Created created = new Created<>("执行");
              created.eachList(list).eachMap(map, true, created.getT());
          }
      
          public Created eachList(List list) {
              // 可以直接对 list 进行遍历查看,
              // (接收的数据类型是Object)
              for (Object o : list) {
      
              }
              // 可以获取 list 集合中的元素
              // (返回的数据类型是Object)
              // 如果集合中没有元素 就会引发: IndexOutOfBoundsException 异常!
              Object o = list.get(0);
              System.out.println(o);
              // 可以添加、设置 null 值到集合中。但这没有任何的意义
              list.add(null);
              System.out.println(list);
              list.set(0, null);
              System.out.println(list);
              // 不可添加其他引用数据类型的 实例。因为 ? 通配符表示是未知的不可以预料的。
              // 假如可以 添加一个字符串 对象到 list 集合中,
              // 那万一调用方法时,传入的集合的泛型的数据类型是 Integer 呢?
              // 那不是类型不匹配,自相矛盾,违背了 泛型的 限制要求、监测机制吗?
      
              // 可以删除 list 集合中的元素,前提是集合要有这个索引的元素,
              // 不然抛出 IndexOutOfBoundsException
              System.out.println("Objects.isNull(list.remove(0)) = " + Objects.isNull(list.remove(0)) + "\n");
              return this;
          }
      
          public  void eachMap(Map map, C c, T t) {
              if (c.equals(true) && "执行".equals(t)) {
                  // 可以对Map集合遍历查看元素
                  for (Map.Entry entry : map.entrySet()) {
                      System.out.println(entry.getKey() + "==" + entry.getValue());
                  }
                  try {
                      // 可以对Map集合添加、设置 null值到Map中(但这没有任何的意义!)
                      map.put(null, null);
                  } catch (Exception e) {
                      e.printStackTrace();
                  } finally {
                      System.out.println("map = " + map.getClass().getSimpleName() + map);
                      // 可以 获取 Map集合中的键值对
                      System.out.println("map.get(\"1\") = " + map.get("1"));
                      // 可以删除Map集合中的键值对(前提是要有这个ke
                      System.out.println("map.remove(\"1\") = " + map.remove("1") + "\n");
                  }
              }
          }
      } test:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第35张图片
      
      
      

      泛型的上下限(常用):

      泛型的上限通配:

      • 声明泛型的上限多种语法: 、、<? extends T>、
      • 结合性: 从左 向右  
      • 泛型的上限。即 ceiling (天花板),使用如下:
        • 不能向 上限通配 的集合中添加元素比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第36张图片因为泛型的上限通配,只是指定了 已知最高的类型是什么 类型,即 这个类型的 子类的 集合可以被这个 泛型上限通配声明的集合 给引用。编译器并不确定 开发者使用哪个子类对象来操作。它只是在通配符的基础上多了一个父类的指定而已 。就是限制了最大是什么类型。编译时。编译器会去检查这个泛型上限通配的集合的 对象 引用 的是不是泛型上限通配的数据类型的子类集合! (capture of ? extend Number e :顾名思义:就是 捕获这个  泛型声明为泛型上限通配  的集合的 对象引用名  引用 的 集合是不是 指定 已知数据类型的子类和已知数据类型它自己本身!起到了区间限制约束的作用!如果是,则编译通过,如果不是 则编译失败 )泛型的 上限通配 支持 指定的数据类型本身的泛型类 和 该数据类型 所有的子类的泛型类 !。(一句概括就是:泛型上限通配的集合之所以不能向里面 添加 子类元素。是因为 extends 是可以是 无限的,而super 是有限的)
        • 让 泛型上限通配 的泛型类声明 指向 子泛型类的实例:比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第37张图片

          面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第38张图片

      泛型的下限通配:

      • 声明泛型的下限的多种语法:、<?super T>、
      • 结合性:从右向左
      • 泛型的下限:即 floor(地板),使用如下
        • 可以向泛型的下限通配的集合中添加元素(只能添加自己和子类的实例,不能添加父类的实例):比如:
          class Underground extends Floor {
              public void m(List list) {
          
              }
          
              public  void unMethod(U u, T t) {
                  super.method(u, t);
              }
          
          }
          
          class Floor extends Center {
              public  void method(S s, T t) {
                  System.out.println(s);
                  System.out.println(t);
              }
          }
          
          class Center extends Fly implements Comparable
          { @Override public int compareTo(@NotNull Center o) { return 1; } } class Fly { }
          面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第39张图片
        • 让 泛型下限通配 的泛型类声明 指向 父泛型类的 实例:比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第40张图片

      关于泛型的通配符、上下限的细节说明:

      1. 首先需要说明一点就是!集合 它本质上也是 Java类,属于Java的泛型类。并不是因为它是集合 就是其他单独的引用类型! 只是 开发者们 约定俗成的喜欢把 集合这种泛型类 ,列为一个单独的引用类型!
      2. 为什么用 泛型通配符 声明的 集合 不能往集合中添加元素?
        1. 理解: 因为 ? 这个泛型通配符代表的是 未知的,无法预知的。既然是 无法预知的 数据类型。那么在声明数据类型的时候,就没有必要去实例化 它的 实例/子类/实例类实例。 用 泛型通配符的意义在于,用 声明的集合可以接收 它的实例/实现类  的 泛型为任意引用数据 类型的 集合。体现了 面向对象 多态的多元化。
        2. 举例:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第41张图片
      3. 为什么用 泛型上限通配的集合 不能往集合中添加元素?:
        1. 理解:因为 泛型上限通配 只是确定了 父类是什么数据类型。但是 并不能够 实际的泛型类型究竟是什么数据类型。假设可以往 泛型上限通配 的集合中添加元素的话。很容易就会造成 类型不匹配的情况(一个类、接口 不只是只能有一个子类、实现类)。编译都不会通过。Java为了避免这种情况。所以不允许往 泛型上限通配 的集合中添加 元素。 泛型上限通配的意义在于: 只是给定了具体的父类。但是具体的子类并不清楚。做到了 由上而下的区间约束。    通俗易懂来说就是: 父类是有限的,子类可以是无限的。 子类不能引用父类实例,但是 父类却可以引用 子类实例,这和Java语言的 继承、多态 有着很大的关系!!(泛型上限通配 还有个作用就是 当这个声明 的引用名 调用方法 去获取元素的时候,只要这个泛型类的 返回值是 当前的 这个 泛型的话,那么默认就会以 泛型上限指定的数据类型来接收!)
        2. 举例:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第42张图片面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第43张图片
        3. 泛型上限通配 声明的 集合(泛型类)引用名 只能够 去 引用 这个集合的泛型 是该引用数据类型的子类 和给定引用数据类型!的集合,而不能去往里面添加元素!面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第44张图片
      4. 为什么用 泛型下限通配 可以往集合中添加 子类的实例?而不能去添加父类的实例呢?面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第45张图片
        1. 理解:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第46张图片
        2. 为什么说: 当子类是泛型类的时候尽量要指定 子类的泛型呢?面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第47张图片
        3. 泛型下限通配 声明的 集合(泛型类)引用名 只能够 去 引用 这个集合的泛型 是该引用数据类型的父类 和 给定的引用数据类型!的集合!面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第48张图片
        4. 泛型下限通配 做到了 由下而上 的区间约束。

      泛型的通配符、上下限的使用场景:

      1. 泛型的通配符   使用场景:当不知道使用的泛型类的泛型需要给定什么样的数据类型的时候 就可以 使用泛型的通配符,比如
        1. 面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第49张图片
      2. 泛型的上限通配 的应用场景:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第50张图片
      3. 泛型的上限通配  、 的应用场景:(泛型方法指的是泛型实例方法,不包括泛型静态方法)面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第51张图片
      4. 泛型的下限通配: 面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第52张图片
      5. 泛型的下限通配: <?super T> 的应用场景:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第53张图片

      泛型的 泛型擦除(编译时泛型擦除、运行时泛型擦除):

      泛型的编译时擦除:

      • 即在使用 泛型类、泛型接口的时候,没有给泛型类的泛型给定具体的数据类型,也没有用 泛型的通配符表示现在不知道,等这个 引用名引用 其他泛型类实例的时候再给定 泛型的具体数据类型 。这种情况下。这个泛型类中所有的泛型标识符都会以被编译成 Object 类型。Object类型是一切类的根据父类。当前是可以接收 所有 类的实例了。比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第54张图片
      • 其他使用泛型类时 实例化 不对的情况下也会造成编译时泛型擦除,比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第55张图片

      泛型的运行时擦除:

      • 说明:
        • 泛型是 JDK1.5 版本才引进的新技术。在这之前是没有泛型的。但是!泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息 只存在于 代码编译阶段,在进入JVM之前,与泛型相关的信息 都会被擦除掉。这种机制 叫做 泛型擦除!
        • 何为代码 编译阶段?:即是 开发者 在用IDE 写代码的时候。这个时候就可以叫做 代码编译阶段。 因为Java 的 编译器的 即时编译技术的 存在,所以 在使用IDE写代码 的时候,就不用去 敲 javac 来手动编译 java文件 。因为 IDE (集成开发环境)就已经集成了 Java 的编译器,也不用 去手动 敲 java 命令手动 来运行 Java 程序,IDE 也集成 Java的运行器 。如果没 集成 ,就不可能有 Run …… .java
      • 泛型擦除 的无限制 类型擦除:

        public class GenericErasureTest {
            public static void main(String[] args) {
                RuntimeErasure erasure = new RuntimeErasure<>();
                erasure.setInfo("什么是快乐星球?");
                // info 这个字段的运行 类型是 String 。
                System.out.println(" info 这个字段的运行 类型是 : " + erasure.getInfo().getClass());
                // 通过反射 查看 RuntimeErasure 这个类的泛型  泛型擦除后类型
                Class clazz = erasure.getClass();
                // 得到 字段实例 数组
                Field[] fields = clazz.getDeclaredFields();
                // 得到 方法实例 数组
                Method[] methods = clazz.getDeclaredMethods();
                for (Field field : fields) {
                    System.out.println("字段名字:" + field.getName()
                            + ", 泛型擦除后 字段类型:" + field.getType());
                }
                for (Method method : methods) {
                    System.out.println("方法名字:" + method.getName() +
                            ", 泛型擦除后 返回值类型:" + method.getReturnType());
                    for (Parameter parameter : method.getParameters()) {
                        System.out.println("参数名字 :" + parameter.getName()
                                +", 泛型擦除后 参数类型:" + parameter.getType());
                    }
                }
            }
        }

        test:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第56张图片

      • 泛型擦除 的有限制 类型擦除(即使用了 泛型的上限通配 )

        public static void erasure() {
                RuntimeErasure erasure = new RuntimeErasure<>();
                erasure.setFiled(6666,"有限制泛型擦除");
                // info 这个字段的运行 类型是 String 。
                System.out.println(" info 这个字段的运行 类型是 : " + erasure.getInfo().getClass());
                System.out.println(" str 这个字段的运行 类型是 : " + erasure.getStr().getClass());
                // 通过反射 查看 RuntimeErasure 这个类的泛型  泛型擦除后类型
                Class clazz = erasure.getClass();
                // 得到 字段实例 数组
                Field[] fields = clazz.getDeclaredFields();
                // 得到 方法实例 数组
                Method[] methods = clazz.getDeclaredMethods();
                for (Field field : fields) {
                    System.out.println("字段名字:" + field.getName()
                            + ", 泛型擦除后 字段类型:" + field.getType());
                }
                for (Method method : methods) {
                    System.out.println("方法名字:" + method.getName() +
                            ", 泛型擦除后 返回值类型:" + method.getReturnType());
                    for (Parameter parameter : method.getParameters()) {
                        System.out.println("参数名字 :" + parameter.getName()
                                + ", 泛型擦除后 参数类型:" + parameter.getType());
                    }
                }
        
        
        class RuntimeErasure {
            private R info;
            private U str;
        
            public R getInfo() {
                return info;
            }
        
            public U getStr() {
                return str;
            }
        
            public void setFiled(R info, U str) {
                this.info = info;
                this.str = str;
            }
        }

        test:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第57张图片

      • 泛型方法的 泛型擦除 :

        public static void erasureMethod() {
                RuntimeErasure erasure = new RuntimeErasure<>();
                erasure.setInfo("泛型擦除");
                erasure.setMap(new HashMap<>());
        
                // info 这个字段的运行 类型是 String 。
                System.out.println(" info 这个字段的运行 类型是 : " + erasure.getInfo().getClass());
                // map 这个字段的运行 类型是 HashMap
                System.out.println(" map 这个字段的运行 类型是 : " + erasure.getMap().getClass());
                Class clazz = erasure.getClass();
                Field[] fields = clazz.getDeclaredFields();
                Method[] methods = clazz.getDeclaredMethods();
                for (Field field : fields) {
                    System.out.println("字段名字:" + field.getName()
                            + ", 泛型擦除后 字段类型:" + field.getType());
                }
                for (Method method : methods) {
                    System.out.println("方法名字:" + method.getName() +
                            ", 泛型擦除后 返回值类型:" + method.getReturnType());
                    for (Parameter parameter : method.getParameters()) {
                        System.out.println("参数名字 :" + parameter.getName()
                                + ", 泛型擦除后 参数类型:" + parameter.getType());
                    }
                }
            }
        }
        
        class RuntimeErasure {
            private R map;
            private U info;
        
            public U getInfo() {
                return info;
            }
        
            public R getMap() {
                return map;
            }
        
            public void setInfo(U info) {
                this.info = info;
            }
        
            public void setMap(R map) {
                this.map = map;
            }
        
            public  S instanceGeneric(S value) {
                return value;
            }
        
            public static  T staticGenricMethod(T value) {
                return value;
            }
        
            public  A instanceGeneric(A a) {
                return a;
            }
        }

        test:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第58张图片

      • 泛型擦除前的桥接方法:

        • 说明:这种关系 只存在于 实现类与泛型接口 、子类与泛型抽象父类 中。这个操作过程 由 编译器完成。

        • 发生这种桥接的前提有三种情况:(跟子类、实现类、子接口是不是 泛型类、泛型接口 无关,生成桥接方法的前提就是,子类、实现类、子接口 在继承、实现时就给定了具体的数据类型!(说明:如果给定的泛型的 引用数据类型是 Object 的话,就不存在 泛型擦除前的桥接方法 !)

          • 第一种:实现类 与 泛型父接口,即泛型父接口的 泛型在实现类 实现的时候 就已经给定了具体的数据类型。这个时候就会有个 桥接方法在 实现类的 class 文件中!比如:

            public static void bridging() {
                    Class clazz = null;
                    try {
                        clazz = Class.forName("indi.jonny.exercises.generic.BridgingImpl");
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    Method[] methods = clazz.getDeclaredMethods();
                    for (Method method : methods) {
                        System.out.println("方法名字:" + method.getName() +
                                ", 方法返回值类型: " + method.getReturnType());
                        for (Parameter parameter : method.getParameters()) {
                            System.out.println("参数名字:" + parameter.getName() +
                                    ",参数类型:" + parameter.getType());
                        }
                    }
                }
            
            interface  Bridging{
                public abstract T bri(T value);
            }
            
            class BridgingImpl implements Bridging{
                @Override
                public String bri(String value) {
                    return value;
                }
            }

            result:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第59张图片这个桥接方法存在的意义就在于,保持泛型父接口和实现类的实现关系。反编译查看字节码文件:,可以理解成为 实际调用 bri 这个方法的时候 实际调用的是 桥接的这个方法,只是在桥接方法中虚拟机又调用 实现类中重写的方法,它的存在表现形式可以理解为:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第60张图片

          • 第二种:子类 继承自 泛型抽象父类 。即泛型抽象父类的 泛型在 子类 继承的时候 就已经给定了具体的数据类型。这个时候就会有个 桥接方法在 子类的 class 文件中!比如:

            public static void bridging() {
                    Class clazz = null;
                    try {
                        clazz = Class.forName("indi.jonny.exercises.generic.SubBridging");
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    Method[] methods = clazz.getDeclaredMethods();
                    for (Method method : methods) {
                        System.out.println("方法名字:" + method.getName() +
                                ", 方法返回值类型: " + method.getReturnType());
                        for (Parameter parameter : method.getParameters()) {
                            System.out.println("参数名字:" + parameter.getName() +
                                    ",参数类型:" + parameter.getType());
                        }
                    }
                }
            
            abstract class AbstractBridging{
                public abstract T bri(T value);
            }
            
            class SubBridging extends AbstractBridging {
                @Override
                public Integer bri(Integer value) {
                    return value;
                }
            }

            result:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第61张图片,这个桥接方法存在的意义就在于,保持泛型抽象父类和 子类的继承关系。反编译查看字节码文件:,可以理解成为 实际调用 bri 这个方法的时候 实际调用的是 桥接的这个方法,只是在桥接方法中虚拟机又调用 实现类中重写的方法,它的存在表现形式可以理解为:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第62张图片

          • 第三种:子接口 继承自 泛型父接口 。即泛型父接口的 泛型在 子接口 继承的时候 就已经给定了具体的数据类型。这个时候就会有个 桥接方法在 子接口的 class 文件中!比如:

            public static void bridging() {
                    Class clazz = null;
                    try {
                        clazz = Class.forName("indi.jonny.exercises.generic.SubBridgingInterface");
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    Method[] methods = clazz.getDeclaredMethods();
                    for (Method method : methods) {
                        System.out.println("方法名字:" + method.getName() +
                                ", 方法返回值类型: " + method.getReturnType());
                        for (Parameter parameter : method.getParameters()) {
                            System.out.println("参数名字:" + parameter.getName() +
                                    ",参数类型:" + parameter.getType());
                        }
                    }
                }
            
            
            
            interface BridgingInterface {
                T bri(T value);
            }
            
            interface SubBridgingInterface extends BridgingInterface {
                @Override
                Double bri(Double value);
            }

            test:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第63张图片这个桥接方法存在的意义就在于,保持泛型父接口 和 子接口 的继承关系。反编译查看字节码文件:,在实现类实现这个子接口的时候,这个桥接方法就会被 继承下去!比如:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第64张图片面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第65张图片

          • test:面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:_第66张图片

      • 往 泛型指定为Double类型的List集合中添加非 Double 的实例:

        • 说明: 只能通过 反射 才可以满足要求 !
          public static void main(String[] args) throws Throwable {
                  class Temp {
                      private T info;
                      private R other;
                      public Temp(T info, R other) {
                          this.info = info;
                          this.other = other;
                      }
                      @Override
                      public String toString() {
                          return "Temp{" +
                                  "info=" + info +
                                  ", other=" + other.getClass() +
                                  '}';
                      }
                  }
                  LinkedList doubleList = new LinkedList<>();
                  doubleList.add(Math.PI);
                  doubleList.add(Math.E);
                  System.out.println(doubleList);
                  // 拿到LinkedList的 add 方法对象。
                  Class linkedClass = doubleList.getClass();
                  // 泛型擦除后,(除了有限制泛型擦除外,)
                  // 所以在泛型类中、泛型接口中、泛型方法中的泛型标识符 都为 Object类型
                  Method add = linkedClass.getMethod("add", Object.class);
                  add.invoke(doubleList,"泛型擦除后的第一个字符串");
                  add.invoke(doubleList,new Temp
                          ("泛型擦除后的第一个对象",
                          BigInteger.valueOf(System.currentTimeMillis())));
                  System.out.println(doubleList);
              }

          result:

       


      下一篇:17 :Collection集合接口:List 集合子接口下的 ArrayList 实现类集合的基本使用

      你可能感兴趣的:(Java,base,sum,up,Java,object,oriented,sum,up,java,泛型)