美文网首页
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泛型

    参考:Java知识点总结(Java泛型) 自定义泛型类 自定义泛型接口 非泛型类中定义泛型方法 继承泛型类 通配符...

  • Java 泛型

    java 泛型 很多朋友对java的泛型不是很理解,很多文章写的已不是很清楚,这篇博客对java泛型进行 一个总结...

  • java泛型

    本质:类型参数化 java总结——泛型

  • Java泛型教程

    Java泛型教程导航 Java 泛型概述 Java泛型环境设置 Java泛型通用类 Java泛型类型参数命名约定 ...

  • java 泛型

    很多朋友对Java的泛型不是很理解,很多文章写的已不是很清楚,这篇博客对java泛型进行 一个总结。 泛型的转换:...

  • 第二十八课:泛型

    泛型出现之前 泛型出现之后 Java深度历险(五)——Java泛型

  • Kotlin 泛型

    说起 kotlin 的泛型,就离不开 java 的泛型,首先来看下 java 的泛型,当然比较熟悉 java 泛型...

  • java泛型中类型擦除的一些思考

    java泛型 java泛型介绍 java泛型的参数只可以代表类,不能代表个别对象。由于java泛型的类型参数之实际...

  • Java泛型总结

    Java泛型总结# 泛型是什么## 从本质上讲,泛型就是参数化类型。泛型十分重要,使用该特性可以创建类、接口以及方...

  • 泛型程序设计

    (Java基础篇都是根据《Java核心技术 卷I》再进行自己的总结归纳和思考写出的) 泛型是什么? 泛型字面上就是...

网友评论

      本文标题:Java泛型总结

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