JAVA反射应用-泛型-通配符

参考https://blog.csdn.net/u012345683/article/details/74858471
https://www.cnblogs.com/fengmingyue/p/6087031.html
https://www.cnblogs.com/lwbqqyumidi/p/3837629.html

我们为什么需要泛型??

1.在1.5之前是没有泛型的,而通常使用object来泛化我们所有的对象,这样做也可以让我们达到泛型的目的,但是在代码编写的过程中很容易出现类型转换的错误,这种错误在编译期间是不知道的,只有到运行期间才知道。
比如:

List list=new List();
  list.add("aaaa");
  list.add(12);
  int a= (int) list.get(0); //代码编写不报错,运行出错
  String s=(String) list.get(1);//代码编写不报错,运行出错

在上面的代码中,编译器只会编译出 list.get(0);返回的是object对象,而传给int a引用变量,是不会报错的,因为任何类型的父类都是object类型。
但是真正运行的时候会报错,原因就是运行期间虚拟机会找到list.get(0);的真正类型是String类型,传递给int a
肯定会出错的。
这些都是码代码的时候最容易出现的错误,这时候泛型类型出现了,在下面的2中给大家解答。
2. java语言的泛型基本上完全是在编译器中实现的,有编译器执行类型检测和类型推断,然后生成普通的非泛型的字节码,然而虚拟机完全无感知泛型的存在。这种实现技术称为擦除(erasure)。编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除。
代码实例:

List<String> list=new List<String>();
  list.add("aaaa");
  list.add(12); //报错
  int a=list.get(0);//报错
  String s=list.get(1);//报错

这里写图片描述
这里写图片描述

在我们编写这些代码的时候是有提示报错的,为啥呢?这就是泛型在编译期间起的作用了。在编译期间,编译器会将泛型中初始化的类型记住,在该泛型对象set/get操作的时候,自动给泛型对象进行类型检查和强制转型操作,所以出错就会在编译期间出现,而不会等到运行期间才发现错误。而在编译器将泛型类编译完成之后,泛型类的类型参数都被全部擦除。
类参数初始化的泛型类其实是共用的一个泛型类字节码,而并不会一种类参数就生成一种对应的泛型类字节码(不太理解)。故才有,我们所说的 泛型的类型只在编译期间有效,运行期间jvm是看不见泛型的具体类型的。也可以看出来java的泛型实现其实就是编译器自动给字节码生成对应的安全操作代码,虚拟机只负责执行而已,泛型完全是由编译器实现的。(虚拟机执行啥?)

那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?

public class TestGeneric {

    public static void main(String[] args) {

                  Box<String> name = new Box<String>("corn");
                  Box<Integer> age = new Box<Integer>(712);

                  System.out.println("name class:" + name.getClass());      // com.qqyumidi.Box
                  System.out.println("age class:" + age.getClass());        // com.qqyumidi.Box
                  System.out.println(name.getClass() == age.getClass()); // true
                  System.out.println(name);
                  System.out.println(age);

             }

      }
     class Box<T> {

         private T data;

         public Box() {

         }

         public Box(T data) {
             this.data = data;
         }

         public T getData() {
             return data;
         }

    } 

输出

name class:class 测试区.Box
age class:class 测试区.Box
true
测试区.Box@1db9742
测试区.Box@106d69c

由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。

究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

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

类型通配符

接着上面的结论,我们知道,Box<Number>Box<Integer>实际上都是Box类型,现在需要继续探讨一个问题,那么在逻辑上,类似于Box<Number>Box<Integer>是否可以看成具有父子关系的泛型类型呢?
看以下代码

import java.util.List;

public class TestGeneric {

    public static void main(String[] args) {
        Box<Number> name = new Box<Number>(99);
        Box<Integer> age = new Box<Integer>(712);  

        getData(name); 
        //The method getData(Box<Number>) in the type GenericTest is 
        //not applicable for the arguments (Box<Integer>)
        getData(age);   // 1     

    }

     public static void getData(Box<Number> data){
         System.out.println("data :" + data.getData());//??静态方法调用非静态方法getdata可以吗???
     }

     class Box<T> {

         private T data;

         public Box() {

         }

         public Box(T data) {
             this.data = data;
         }

         public T getData() {
             return data;
         }

    }
}

我们发现,在代码//1处出现了错误提示信息:The method getData(Box<Number>) in the t ype GenericTest is not applicable for the arguments (Box<Integer>)。显然,通过提示信息,我们知道Box<Number>在逻辑上不能视为Box<Integer>的父类,number是Integer的父类。那么,原因何在呢?

我们需要一个在逻辑上可以用来表示同时是Box<Integer>Box<Number>的父类的一个引用类型,由此,类型通配符应运而生。

类型通配符的作用

再用一个代码说明类型通配符的作用

public static void fun(List<Object> list) {…}
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
fun(list1);//编译不通过
fun(list2);//编译不通过

这说明想写一个即可以打印list1,又可以打印list2的方法是不可能的!
如果把fun()方法的泛型参数去除,那么就OK了。即不使用泛型!

public static void fun(List list) {…}//会有一个警告
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
fun(list1);
fun(list2);

上面代码是没有错了,但会有一个警告。警告的原因是你没有使用泛型!Java希望大家都去使用泛型。你可能会说,这里根本就不能使用泛型!!!!!

通配符的概述

通配符就是专门处理这一问题的。

public static void fun(List<?> list) {…}

上面代码中的“?”就是一个通配符,它只能在“<>”中使用。这时你可以向fun()方法传递List<String>、List<Integer>类型的参数了。当传递List<String>类型的参数时,表示给“?”赋值为String;当传递List<Integer>类型的参数给fun()方法时,表示给“?”赋值为Integer。

通配符的缺点

  • 在上面例子中,List<?> list参数中的通配符可以被赋任何值,但同时你也不知道通配符被赋了什么值。当你不知道“?”是什么时,会使你不能使用任何与泛型相关的方法
  • 也就是说fun()方法的参数list不能再使用它的与泛型相关的方法了。例如:list.add(“hello”)是错误的,因为List类的add()方法的参数是T类型,而现在你不知道T是什么类型,你怎么去添加String的东西给list呢?
  • 当然,还可以调用list的get()方法。就算你不知道“?”是什么类型,但它肯定是Object类型的。所以你可以:Object o = list.get(0);

    也就是说,对于List,你可以查看这个接口的源码,里面涉及到了T(泛型)参数的方法,是都不可以使用的,因为使用了通配符你不知道T是什么类型!

  • 通配符只能出现在引用的定义中,而不能出现在创建对象中。例如:new ArrayList<?>(),这是不可以的。ArrayList<?> list = null,这是可以的。

    带有边界的通配符

  • 类型通配符上限
    List<? extends Number> list;
    其中<? extends Number>表示通配符的下边界,即“?”只能被赋值为Number或其子类型。

public static void fun(List<? extends Number> list) {…}
fun(new ArrayList<Integer>());//ok
fun(new ArrayList<Double>());//ok
fun(new ArrayList<String>());//不ok

当fun()方法的参数为List<? extends Number>后,说明你只能赋值给“?”Number或Number的子类型。虽然这多了一个限制,但也有好处,因为你可以用list的get()方法。就算你不知道“?”是什么类型,但你知道它一定是Number或Number的子类型。所以:Number num = list.get(0)是可以的。但是,还是不能调用list.add()方法!

  • 带有下边界的通配符
    List<? super Integer> list;

其中<? super Integer>表示通配符的下边界,即“?”只能被赋值为Integer或其父类型。

public static void fun(List<? super Integer> list) {…}
fun(new ArrayList<Integer>());//ok
fun(new ArrayList<Number>());//ok
fun(new ArrayList<Object>());//ok
fun(new ArrayList<String>());//不ok

这时再去调用list.get()方法还是只能使用Object类型来接收:Object o = list.get(0)。因为你不知道“?”到底是Integer的哪个父类。但是你可以调用list.add()方法了,例如:list.add(new Integer(100))是正确的。因为无论“?”是Integer、Number、Object,list.add(new Integer(100))都是正确的。

通配符的应用

boolean addAll(Collection<? extends E> c)//JDK集合的addAll方法

List<Number> numList = new ArrayList<Number>();
List<Integer> intList = new ArrayList<Integer>();
numList.addAll(intList);//正确!!!!!!addAll(Collection<? extends Number> c), 传递的是List<Integer>

如果用改成boolean addAll(Collection<E> c)

List<Number> numList = new ArrayList<Number>();
List<Integer> intList = new ArrayList<Integer>();
numList.addAll(intList);//错误!!!!!addAll(Collection<Number> c), 传递的是List<Integer>

通配符应用实例

import java.util.ArrayList;
import java.util.List;
public class Demo2 {
    public void fun1() {
        Object[] objArray = new String[10];//正确!!!
        objArray[0] = new Integer(100);//错误!!!编译器不会报错,但是运行时会抛ArrayStoreException
        //List<Object> objList = new ArrayList<String>();//错误!!!编译器报错,泛型引用和创建两端,给出的泛型变量必须相同!
    }


    public void fun2() {
        List<Integer> integerList = new ArrayList<Integer>();
        print(integerList);
        List<String> stringList = new ArrayList<String>();
        print(stringList);
    }
    /*
     * 其中的?就是通配符,?它表示一个不确定的类型,它的值会在调用时确定下来
     * 通配符只能出现在左边!即不能在new时使用通配符!!!
     * List<?> list = new ArrayList<String>();
     * 通配符好处:可以使泛型类型更加通用!尤其是在方法调用时形参使用通配符!
     */
    public void print(List<?> list) {
        //list.add("hello");//错误!!!编译器报错,当使用通配符时,对泛型类中的参数为泛型的方法起到了副作用,不能再使用!
        Object s = list.get(0);//正确!!!但是只是得益于object类是所有类的父类,换成其他任何类编译器都会报错!说明当使用通配符时,泛型类中返回值为泛型的方法,也作废了!
    }


    public void fun3() {
        List<Integer> intList = new ArrayList<Integer>();
        print1(intList);
        List<Long> longList = new ArrayList<Long>();
        print1(longList);
    }
    /*
     * 给通配符添加了限定:
     *   只能传递Number或其子类型
     *   子类通配符对通用性产生了影响,但使用形参更加灵活
     */
    public void print1(List<? extends Number> list) {
        //list.add(new Integer(100));//错误!!!编译器报错,说明参数为泛型的方法还是不能使用(因为?也可能为Long型)
        Number number = list.get(0);//正确!!!返回值为泛型的方法可用了!
    }


    public void fun4() {
        List<Integer> intList = new ArrayList<Integer>();
        print2(intList);
        List<Number> numberList = new ArrayList<Number>();
        print2(numberList);
        List<Object> objList = new ArrayList<Object>();
        print2(objList);
    }
    /*
     * 给通配符添加了限定
     *   只能传递Integer类型,或其父类型
     */
    public void print2(List<? super Integer> list) {
        list.add(new Integer(100));//正确!!!参数为泛型的方法可以使用了
        Object obj =  list.get(0);//正确!!!但是只是得益于object类是所有类的父类,换成其他任何类编译器都会报错!说明返回值为泛型的方法,还是不能使用
    }
}

总结:
通配符是为了解决在方法参数带有泛型类参数的时候,编译的时候会载入泛型参数,而导致这个方法只能代入某个特定泛型参数,而不能给别的参数使用,那么这样就失去了泛型参数的意义了,于是引入了通配符,我理解为表示不知道这个泛型类参数是什么,所以每次调用这个方法的时候通配符都代表着不同泛型类参数,但是同时也带来的一个问题,就是对于带有泛型参数的方法如(list.add)你也不能用了,因为你不知道add的是integer还是string,为了解决这种问题就引入了带有上下边界的通配符,虽然多了一种限制,但是也多了一些信息,你知道了泛型参数的上下限,就可以利用泛型参数的父类和子类等来接受或者带入数据,例如你设计了一个带有下边界的通配符list<? super Integer> list,泛型参数可以为Integer的各种父类,这时候使用list.add(new integer(100)),就可以使用了,因为不论是那个父类,Integer都是他们的子类( 这里有一个方法传入的参数是父类(Parent),那么也可以传入它的子类Son)(关于动态绑定!???学习)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值