面向对象的一个重要目标是对代码重用的支持。支持这个目标的一个重要机制就是泛型机制。在1.5版本之前,java并没有直接支持泛型实现,泛型编程的实现时通过使用继承的一些基本概念来完成的。

这种方式的局限性有:
1. 使用此种方式会不可避免地用到强制类型转换。
2.
不能使用基本类型,只有引用类型能和Object相容。(通过使用包装器类)

例如使用Comparable接口来暂时代表所有实现了该接口的类。

什么是协变性?

简而言之,如果A IS-A B,那么A[] IS-A B[]。
举例:现在有类型Person、Employee和Student。Employee
是一个(IS-A) Person,Student是一个(IS-A)Person。那么下面的语句可以通过编译:

但是上面的代码在运行时却会出错。因为arr[0]实际上是引用一个Employee,可是Student IS-NOT-A
Employee。这样就产生了混乱。这种错误正是由于Java数组的协变性而产生的。那么Java为什么不禁止数组协变呢?

因为SE5之前还没有泛型,但很多代码迫切需要泛型来解决问题。 例如:

Arrays.equals()方法的底层实现调用的是Object.equals()方法,和数组中元素的具体类型无关,这充分利用了Java中任何类型都继承自Object类的特性,避免了为每个类型都重新定义Arrays.equals()方法。而在没有泛型的时代,要让Object[]能接受所有数组类型,最简单的办法就是让数组接受协变,把String[],Integer[]都定义成Object[]的派生类,然后多态就起作用了。

为什么数组设计成”协变“不会有大问题呢?

这是基于数组的一个独有特性:

数组记得它内部元素的具体类型,并且会在运行时做类型检查。

因为arr[0]记得它内部的元素类型是Employee,所以运行时给它插入一个Student类型会报错。

这个特性使得Java数组协变带来的影响不会酿成大错——错误最终还是会被检测出来,只不过是从编译时推迟到了运行时。

正是有这个特性,Java当初才敢于把数组设计成协变的。虽然向上转型以后,编译期类型检查放松了,但因为数组运行时对内部元素类型的严格检查,不匹配的类型还是插不进去的。这也是为什么容器Collection不能设计成协变的原因——Collection不做运行时类型检查。

简单泛型类

简单泛型接口

如果一个int型量被传递到需要一个Integer对象的地方,那么,编译器将在幕后插入一个对Integer构造方法的调用以获得Integer对象。这就叫做自动装箱

反过来,如果一个Integer对象被放到需要int型量的地方,则编译器将在幕后插入一个对intValue方法的调用以获得int值,这就叫做自动拆箱

对于其他7对基本类型/包装类型,同样会发生类似的情形。

Java7增加了一种新特性,称作菱形运算符。使得下面的代码:

可以写成:

菱形运算符在不增加开发者负担的情况下简化了代码。

在Java中,数组是协变的,如果B IS-A C,那么B[] IS-A C[]
。但是集合Collection不是协变的,这就使得集合缺少灵活性。为了弥补这个不足,Java5引入了通配符。举例如下

既然有那么也有与之对应的,他们分别为泛型参数的上界和下界。

1.泛型类

2.泛型方法

泛型方法分为两种,区别在于是否带特定参数列表。

  • 一、 不带特定参数列表的泛型方法

因为它能接受不同类型的参数,所以,它是泛型方法。

  • 二、带特定参数列表的泛型方法

通过在返回类型前声明特定参数,能够获得以下好处:

  • 可以将T用作返回类型
  • 不止一个方法参数的声明需要用到T
  • 将T用于声明局部变量

泛型方法与泛型类很相似,因为参数列表使用相同的语法。在泛型方法中,泛型参数的声明在返回类型之前,而泛型类在类名之后。

一、什么是类型擦除

让我们看一个有趣的例子:

和很明显是不同的类型。但是上面的程序却认为它们是相同的类型。因此,存在一个残酷的现实:

在泛型代码内部,无法获得任何有关泛型参数类型的信息。

Java泛型是使用擦除来实现的,这意味着当你在使用泛型的时候,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此和在运行时事实上是相同的类型。这两种类型都被擦除成了他们的“原生”类型,即List。

再看另外一个例子:

类似的代码,在c++中能够正常运行。但是由于类型擦除,Java编译器无法将useF()能够在obj上调用f()这一需求映射到A拥有f()这一事实上。在类B中,由于T没有指定上下界,于是T只拥有默认的上界Object(等同于),因此在泛型方法中我们只能调用Object的方法。类似的,如果我们想要在泛型方法中使用泛型参数的方法,那么我们必须设定上下界。下面的代码就可以编译了:

边界声明T必须具有类型A或者从A导出的类型。

泛型参数将擦除到它的第一个边界。编译器实际上会把类型参数替换为它的擦除。就像上面的示例那样,T擦除到了A。.

二、怎么看待类型擦除

类型擦除不是一个语言特性,它只是一种折中。因为Java1中没有泛型,泛型是后来加入的。为了不影响现有的类库和已经在使用的代码,Java没有采用C++等语言那样彻底的泛型,而是使用类型擦除这种温和但有缺陷的方式类让Java拥有泛型。

擦除的代价是明显的。泛型不能用于显示地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。因为所有关于参数的类型信息都丢失了,无论何时,当你在编写泛型代码时,必须时刻提醒自己,雅思考试报名官网你只是看起来拥有有关参数的类型信息而已,你实际操作的只是一个Object。

  • 基本类型不能用作类型参数,类型参数必须是引用类型。必须使用包装类。
  • 不能用instanceof检测泛型,由于类型擦除的原因,任何泛型类型都会别擦除为它的原生类型。
  • 泛型类中,static方法和static域不能引用类的泛型变量。因为在类型擦除过后,类型参数就不存在了。同时,我们知道同一个类的所有实例共用类的static域和static方法,如果静态域接受泛型参数,那么这个参数到底是类型参数的哪一种实际类型就无法确定了。
  • 不能创建一个泛型类型的实例。是非法的。
  • 不能创建一个泛型数组,因为数组有严格的类型检查。通常用泛型容器,如来实现同样的需求。

关于Java泛型实现原理的思考与一般用法示例总结的更多相关文章

  1. Java泛型-内部原理: 类型擦除以及类型擦除带来的问题

    一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...

  2. 关于Java泛型"擦除"的一点思考

    头次写博客,想说的东西不难,关于泛型的疑问,是前一阵在学习jackson中遇到的. 下面就把我所想到的.遇到的,分享出来. 泛型是JDK1.5后的一个特性,是一个参数类型的应用,可以将这个参数声明在类 ...

  3. JAVA泛型实现原理

    1. Java范型时编译时技术,在运行时不包含范型信息,仅仅Class的实例中包含了类型参数的定义信息.泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的.你可以(基本上就是)把它 ...

  4. java 泛型实现原理

    泛型思想最早在C++语言的模板(Templates)中产生,Java后来也借用了这种思想.虽然思想一致,但是他们存在着本质性的不同. C++中的模板是真正意义上的泛型,在编译时就将不同模板类型参数编译 ...

  5. java SequenceInputStream类(序列输入流)的用法示例

    public class SequenceInputStreamextends InputStream SequenceInputStream 表示其他输入流的逻辑串联.它从输入流的有序集合开始,并从 ...

  6. java泛型学习(2)

    一:深入泛型使用.主要是父类和子类存在泛型的demo /** * 父类为泛型类 * @author 尚晓飞 * @date 2014-7-15 下午7:31:25 * * * 父类和子类的泛型. * ...

  7. Java泛型总结---基本用法,类型限定,通配符,类型擦除

    一.基本概念和用法 在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化.例如在哈希表的存取中,JDK1.5之前使用HashMap的 ...

  8. 浅析Java泛型

    什么是泛型? 泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型.这种参数类型 ...

  9. Java 泛型 <? super T> 中 super 怎么 理解?与 < ? extends T>有何不同?

    Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同? 简介 前两篇文章介绍了泛型的基本用法.类型擦除以及泛型数组.在泛型的使用中,还有个重要的 ...

随机推荐

  1. 关于Java多线程的一些面试问题

    1.ArrayList和Vecoter区别? Array和ArrayList的异同点一.Array和ArrayList的区别#1. Array类型的变量在声明的同时必须进行实例化(至少得初始化数组的大 ...

  2. java基础语法详细介绍

    一.概述 1.java语言概述 是SUN(Stanford University Network,斯坦福大学网络公司 ) 1995年推出的一门高级编程语言; java之父---James Goslin ...

  3. ambari 2.5.0源码编译安装

    参考:https://www.ibm.com/developerworks/cn/opensource/os-cn-bigdata-ambari/index.html Ambari 是什么 Ambar ...

  4. Antd时间选择框汉化问题总结------国际化全局设置

    import zh_CN from 'antd/lib/locale-provider/zh_CN'; import 'moment/locale/zh-cn'; import { ConfigPro ...

  5. iOS之Run Loop详解

    转自标哥的技术博客(www.henishuo.com) 前言 做了一年多的IOS开发,对IOS和Objective-C深层次的了解还十分有限,大多还停留在会用API的级别,这是件挺可悲的事情.想学好一 ...

  6. python中的面向对象和面向过程

    一.面向对象和面向过程 一.什么是面向过程 核心是过程二字:面向过程是一种流水线的工作流程,是先做什么在做什么 二.什么是面向对象 核心 是对象二字:是一种编程思想,上帝思维,一切皆对象,编程中负责调 ...

  7. 磁盘IO性能优化-实践

    RAID卡缓存策略调整 原因详解 操作实例 I/O 调度算法 文件系统journal 磁盘挂载参数 操作实例 性能数据对比 RAID卡缓存策略调整 可以将RAID卡缓存策略由No Write Cach ...

  8. 10年前文章_respin 下制作iso 文件的脚本说明

    1.prepare_spin.sh 用于在 /var/rpm 下生成  lhs-local 需要的repositery 2.respin.sh 使用revisor 生成 iso 3. post_spi ...

  9. python 导入模块、包

    1. 模块:一个有逻辑的python文件,包含变量.函数.类等.2. 包:一个包含__init__.py的文件夹,存放多个模块 import 本质是路径搜索,查找sys.path下有无你导入的 pac ...

  10. java ThreadGroup源码分析

    使用: import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction; public class Test { public s ...