zoukankan      html  css  js  c++  java
  • 深入理解泛型

    引入泛型的意义何在?

    • 泛型的提出是为了编写重用性更好的代码。
    • 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

      在未引入泛型之前,需要用Object来实现通用、不同类型的处理。

      缺点如下:

    • 每次使用时都需要强制转换成想要的类型。
    • 在编译时编译器并不知道类型转换是否正常,运行时才知道,不安全。

    实际上引入泛型的主要目标有以下几点:

    类型安全 :

    • 泛型的主要目标是提高 Java 程序的类型安全
    • 编译时期就可以检查出因 Java 类型不正确导致的 ClassCastException 异常
    • 符合越早出错代价越小原则

    消除强制类型转换 :

    • 泛型的一个附带好处是,使用时直接得到目标类型,消除许多强制类型转换
    • 所得即所需,这使得代码更加可读,并且减少了出错机会潜在的性能收益 

    潜在的性能收益:

    • 由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改
    • 所有工作都在编译器中完成
    • 编译器生成的代码跟不使用泛型(和强制类型转换)时所写的代码几乎一致,只是更能确保类型安全而已

    泛型的使用

      泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

    这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法

    泛型类:泛型类最常见的用途就是作为容纳不同类型数据的容器类,比如 Java 集合容器类。

    泛型接口:实现类在实现泛型接口时需要指明具体的参数类型,不然默认类型是 Object类型。

    泛型方法:如果所在的类是泛型类,则直接使用类声明的参数,如果不是,则需自己声明参数类型。

     

    泛型通配符

    <?>:无限制通配符,表示可以持有任何类型。

    <?>和<Object>不一样,<?>表示未知类型,<Object>表示任意类型。

     

    <? extends E>:

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

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

    <? super E>:

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

    小结:

    • 无限制通配符<?> 和 Object 有些相似,用于表示无限制或者不确定范围的场景
    • 两种有限制通配形式 < ? super E> 和 < ? extends E> 也比较容易混淆,我们再来比较下。
    • 它们的目的都是为了使方法接口更为灵活,可以接受更为广泛的类型
    • < ? super E> 用于灵活写入或比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象。
    • < ? extends E> 用于灵活读取,使得方法可以读取 E 或 E 的任意子类型的容器对象。

    使用通配符的基本原则:

    • 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
    • 如果它表示一个 T 的消费者,就使用 < ? super T>;
    • 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。
    • T 的生产者的意思就是结果会返回 T,这就要求返回一个具体的类型,必须有上限才够具体;
    • T 的消费者的意思是要操作 T,这就要求操作的容器要够大,所以容器需要是 T 的父类,即 super T;

    泛型的类型擦除

    用泛型编写的 Java 程序和普通的 Java 程序基本相同,只是多了一些参数化的类型同时少了一些类型转换

    实际上泛型程序也是首先被转化成一般的、不带泛型的 Java 程序后再进行处理的,编译器自动完成了从 Generic Java 到普通 Java 的翻译,Java 虚拟机运行时对泛型基本一无所知。

    当编译器对带有泛型的java代码进行编译时,它会去执行类型检查和类型推断,然后生成普通的不带泛型的字节码,这种普通的字节码可以被一般的 Java 虚拟机接收并执行,这在就叫做 类型擦除(type erasure)

    总之,泛型就是一个语法糖,它运行时没有存储任何类型信息。

    泛型的情况称为不可变性,与之对应的概念是协变、逆变:

    • 协变:如果 A 是 B 的父类,并且 A 的容器(比如 List<A>) 也是 B 的容器(List<B>)的父类,则称之为协变的(父子关系保持一致)。
    • 逆变:如果 A 是 B 的父类,但是 A 的容器 是 B 的容器的子类,则称之为逆变(放入容器就篡位了)。
    • 不可变:不论 A B 有什么关系,A 的容器和 B 的容器都没有父子关系,称之为不可变。

    Java 中数组是协变的,泛型是不可变的。

      如果想要让某个泛型类具有协变性,就需要用到边界

    • 我们知道,泛型运行时被擦除成原始类型,这使得很多操作无法进行。
    • 如果没有指明边界,类型参数将被擦除为 Object。
    • 如果我们想要让参数保留一个边界,可以给参数设置一个边界,泛型参数将会被擦除到它的第一个边界(边界可以有多个),这样即使运行时擦除后也会有范围。

    泛型的规则

    • 泛型的参数类型只能是类(包括自定义类),不可以是简单类型。
    • 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
    • 泛型的类型参数可以有多个
    • 泛型的参数类型可以使用 extends 语句,习惯上称为“有界类型”。
    • 泛型的参数类型还可以是通配符类型,例如 Class。

    泛型的使用场景

    当类中要操作的引用数据类型不确定的时候,过去使用 Object 来完成扩展,JDK 1.5后推荐使用泛型来完成扩展,同时保证安全性

     

    Java中List<Object>和原始类型List之间的区别?

    原始类型和带参数类型之间的主要区别是:

    • 在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行安全检查。
    • 通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。
    • 你可以把任何带参数的类型传递给原始类型 List,但却不能把List<String> 传递给接受List<Object>的方法,因为泛型的不可变性,会产生编译错误。

     

     

  • 相关阅读:
    判断Redis复制是否完成的方法
    jquery ajax 设置header的方式
    二维码 halcon(转)
    转 python 简易windows编辑器
    转 【ORACLE 12C】ORA-65066
    转 python 2 读取配置文件
    转 MySQL active threads more than 40 on db3.***.com
    推荐一个画连锁不平衡图(LD block)的软件LDBlockShow,亲测比haploview好用!
    ImportError: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found解决方法
    一个R包(IntAssoPlot),LocusZoom图、连锁不平衡图和基因结构图一步到位
  • 原文地址:https://www.cnblogs.com/crazypokerk/p/9290555.html
Copyright © 2011-2022 走看看