美文网首页
Java泛型总结

Java泛型总结

作者: 技术咸鱼 | 来源:发表于2021-05-27 15:35 被阅读0次

    原文链接
    技术咸鱼

    什么是Java泛型

      Java泛型(Java generic)是JDK 5引入的一个新特性.其本质就是参数化类型,也就是把数据类型视作为一个参数,在使用的时候再指定具体类型,这种参数化类型就是泛型.泛型可以用在类,接口,方法上,分别称之为泛型类,泛型接口,泛型方法.
      泛型的出现为程序员提供了一种编译时类型安全的监测机制,使程序员能够在编译期间找出非法类型的存在,提高开发的安全性和效率.
      Java中的泛型一种伪泛型,使用了类型擦除实现的,本质上是Java语言的语法糖.


    为什么出现Java泛型

    看下面的情形

    public int add(int a, int b) {
            return a + b;
        }
    
        public float add(float a, float b) {
            return a + b;
        }
    
        public double add(double a, double b) {
            return a + b;
        }
    

      我们为了实现不同数据类型的add方法,就需要给每种类型都写一个重载方法,这显然不符合我们开发的需求.

      如果我们在这里使用泛型的话,就不需要给每种数据类型都增加一个重载方法.

    public <T extends Number> double add(T a, T b) {
            return a.doubleValue() + b.doubleValue() ;
        }
    

      如果我们在这里使用泛型的话,就不需要给每种数据类型都增加一个重载方法.

    再看下面的情形

    step1

      在上面代码我们定义了一个List类型的集合,先向里面加入了两个String类型的值,然后又加入了一个Integer类型的值,这在Java编译期间是允许的,因为List默认的类型是Object.在后面的循环中,因为之前加入的数据类型不一样,很可能出现ClassCastException异常.

    从上我们可以大概总结到泛型有以下好处
    <font color=#ff0000>1,代码复用,增加代码拓展性</font>
    <font color=#ff0000>2,消除强制类型转换,类型安全</font>

    泛型的使用

      泛型可以用在类,接口,方法上,分别可以称为泛型类,泛型接口,泛型方法.

    泛型类/泛型接口

      泛型类和泛型接口的定义基本相同,就是引入一个类型变量T(其他大写字母也OK,一般约定俗成的有T,E,V,K,N等),并用<>括起来,放在类名/接口名的后面,泛型类和接口允许有多个类型变量

    //泛型类(1)
    public class GenericClass<T> {
        private T data;
    }
    //泛型类(2)
    public class GenericClass<T,K> {
        private T data;
        private K result;
    }
    
    //泛型接口
    public interface IGeneric<T> {
        T getData();
    }
    

    泛型方法

      泛型方法,是在调用方法的时候指明泛型的具体类型,泛型方法可以在任意地方任意场景中使用,包括普通类和泛型类.

        //普通方法
        public T getData() {
            return data;
        }
        
        //泛型方法
        public <V> V handleData(V data) {
            return data;
        }
    

      <font color=#ff0000>一定要注意的是,并不是方法中参数或者返回值包含了泛型的就是泛型方法,只有在调用的时候需要指明泛型的才是泛型方法.</font>

    限定类型变量

      有时候,我们需要对泛型的类型进行限定,比如说我们写一个泛型方法比较两个变量的大小,我们怎么确保传入的两个变量都一定有compareTo方法呢?这个时候我们就可以使用T extends Comparable对泛型的类型变量进行限制,


    step2

      T表示应该绑定类型的子类型,Comparable表示绑定的类型,子类型和绑定类型可以试类,也可以是接口
      这个时候如果我们试图传入一个没有实现接口Comparable的实例变量,将会发生变异错误


    step3

    泛型类的继承规则

    <font color=#0099FF>1,泛型参数是继承关系的,泛型类之间没有继承关系</font>

    public class Animal {}
    
    public class Cat extends Animal { }
    
    public class Pet<T> { }
    public class ExtendPet<T> { }
    
    Pet<Animal> genericClass =new Pet<Cat>(); //错误
    
    

    <font color=#0099FF>2,泛型类是可以继承其他泛型类的,比如List和ArrayList</font>

    public class Animal {}
    
    public class Cat extends Animal { }
    
    public class Pet<T> { }
    public class ExtendPet<T> { }
    
    Pet<Animal> genericClass =new ExtendPet<>(); //正确
    

    通配符使用

    数组的协变

      在讲泛型通配符之前,我们先了解下数组,在Java中,数组是可以<font color=#0099FF>协变</font>的,什么是<font color=#0099FF>协变</font>,我们以下面的例子讲解下.

    public class Animal { }
    
    public class Dog extends Animal { }
    
    public class JingBa extends Dog{ }
    
    public class Cat extends Animal { }
    
    public class GenericClass {
        public static void main(String[] args) {
           Animal[] animals = new Dog[5];
           animals[0] = new Dog();//可以
           animals[1] = new JingBa();//可以
           System.out.println(animals.getClass());
           System.out.println(animals[0].getClass());
           System.out.println(animals[1].getClass());
           //animals[2] = new Animal();//ArrayStoreException
           // animals[3] = new Cat();//ArrayStoreException
        }
    }
    
    
    //打印结果
    ----------------------------------------------------
    class [Lcom.company.genneric.Dog;
    class com.company.genneric.Dog
    class com.company.genneric.JingBa
    ----------------------------------------------------
    

      在上面的代码中,创建一个Dog数组并将他赋值给Animal数组的引用.像这种具有子父类关系的类,子类数组也是父类数组的情况就是<font color=#0099FF>数组协变</font>
      不过在使用<font color=#0099FF>数组协变</font>,也有些事情要注意,就是有些问题在运行的时候才能发现.还是看上面的代码.尽管Dog[]可以"向上转型"为Animal[],但是数组中实际的类型还是Dog对象,我们在上面放入Dog或者Dog的子类JingBa的对象时都是可以的,但是在放入Animal或者Cat对象的时候会在运行的时候报ArrayStoreException异常.
      泛型设计的目的之一就是将一些运行时候的错误暴露在编译期间,我们下面使用Java提供的泛型容器List,看下会发生什么.

        public static void main(String[] args) {
            ArrayList<Animal> list = new ArrayList<Dog>();
        }
    
    step7

      看到了,上面的代码根本无法编译,直接报错,当涉及到泛型的时候,尽管Dog是Animal的子类,ArrayList<Dog>却不是ArrayList<Animal>的子类,也就是说泛型不支持<font color=#0099FF>协变</font>

    通配符的使用

      如果我们要实现类似上面数组的<font color=#0099FF>协变</font>怎么办呢,这时候我们就用到了通配符.Java中泛型通配符分为3种,我们依次来讲.

    上边界通配符

      使用<? extends Parent>的就是上边界通配符,他指定了泛型类型的上边界,类型参数都是Parent的子类,通过这种通配符可以实现泛型的"向上转型".

        public static void main(String[] args) {
            List<? extends Animal> list = new ArrayList<Dog>();
    
    //        list.add(new Dog());//编译错误,Compile Error:cant't add any type of object
    //        list.add(new JingBa());//编译错误,Compile Error:cant't add any type of object
    //        list.add(new Cat()));//编译错误,Compile Error:cant't add any type of object
    //        list.add(new Object()));//编译错误,Compile Error:cant't add any type of object
            list.add(null);
            Animal a = list.get(0);
        }
    

      在上面的代码中,list的类型是List<? extends Animal>,可以把list看成是一个类型的List,这个类型是可以继承Animal的,但是需要注意的是,这并不是说这个List就可以持有Animal的任意类型.通配符代表的是某种特定的类型,但是上面的list没有指定实际的类型,它可以是Animal的任何子类型,Animal是它的上边界.

      既然我们不知道这个list是什么类型,那我们如果安全的添加一个对象呢?在上面的例子中我们也看到了,无论是添加Dog,JingBa,Cat还是Object对象,编译器都会报错,唯一能通过编译的就是null.所以如果我们写了向上转型<? extends Parent>的泛型那么我们的List将失去添加任务对象的能力,及时Object对象也不行.

      另外如果我们获取返回Animal的方法,这是可以的,因为在这个list中,不管它实际的类型到底是什么,肯定可以转型成Animal的,所有向上转型返回数据是允许的.

    <font color=#0099FF>总结:主要用于安全地访问数据,可以访问Parent及其子类型,并且不能写入非null的数据</font>

    下边界通配符

      使用<? super Child>的就是下边界通配符,他指定了泛型类型的下边界,类型参数都是Child的基类,通过这种通配符可以实现泛型的"向下转型".

            List<? super Dog> list = new ArrayList<>();
            list.add(new Dog());
            list.add(new JingBa());
            list.add(null);
    //        list.add(new Cat());//编译错误,Compile Error:cant't add any type of object
    //        list.add(new Object());//编译错误,Compile Error:cant't add any type of object
            Object object = list.get(0);
    
    

      在上面的代码中,我们也不能确定list里的是什么类型,但是我们知道这个类型一定是Dog的基类(父类),因此我们向里面添加一个Dog对象或者Dog子类型的对象是安全的,这些对象都可以向上转型为Dog.我们在取出list里面的数据的时候,返回的一定是Dog的基类(父类),但到底是哪一个基类(父类)我们是不知道的,但在java中所有的类型都继承自Object,所有在list取出的数据,返回的一定是Object.

    <font color=#0099FF>总结:主要用于安全地写入数据,可以写入Child及其子类型</font>

    无边界通配符

    <?>无边界通配符,没有任何限定.

            List<?> list = new ArrayList<>();
    //        list.add(new Animal());//编译错误,Compile Error:cant't add any type of object
    //        list.add(new Dog());//编译错误,Compile Error:cant't add any type of object
    //        list.add(new Cat());//编译错误,Compile Error:cant't add any type of object
    //        list.add(new Object());//编译错误,Compile Error:cant't add any type of object
            list.add(null);
            Object object = list.get(0);
    

      List<?>表示持有某种特定类型的List,但是这种List并没有指定具体类型,这是不安全的,所以我们不能向里面添加除null以外的对象

    List<?>与List的区别?

      List没有泛型参数,表明这个List持有元素的类型是Object,因此可以添加任何类型的对象,不过编译器会有警告信息.

    泛型中的约束和局限性

    <font color=#ff0000>1,不能用基本数据类型实例化类型参数</font>


    step4

    <font color=#ff0000>2,运行时类型查询只适用于原始类型</font>


    step5

    <font color=#ff0000>3,泛型类的静态变量或者方法不能使用泛型类型</font>
    注:静态方法本身就是泛型方法除外


    step6

      不能在静态方法和变量中引用泛型类型变量,因为泛型是在对象创建的时候才知道是什么类型,而对象创建代码的执行顺序是static,构造方法,所以在对象初始化之前static的部分已经执行了.

    <font color=#ff0000>4,不能创建泛型数组</font>


    step8

    虚拟机中泛型的实现-类型擦除

      Java泛型是在Java1.5以后才出现的,在Java早期版本中并没有泛型概念,为了向下兼容,Java泛型只存在在编译期,在编译后,就会替换成原生类型,并在相应的地方插入强制转换类型的代码,因此对于运行期的Java语言来说,ArrayList<Int>和ArrayList<String>就是一个类,这种编译后去除类型信息的方式就叫做类型擦除.

    Class c1 = new ArrayList<String>().getClass();
            Class c2 = new ArrayList<Integer>().getClass();
            System.out.println(c1==c2);
    

      泛型参数会擦除到他的第一个边界,如果参数类型是单独的一个T,那么最终会擦除到Object,相当于所有使用T的地方都会被Object替换,对于虚拟机来说,最终保存的参数类型还是Object.之所以还可以取出来我们传入的参数类型,是因为编译器在编译生成字节码的过程中,插入了类型转换的代码.

    原文链接
    技术咸鱼

    相关文章

      网友评论

          本文标题:Java泛型总结

          本文链接:https://www.haomeiwen.com/subject/xtyngktx.html