java泛型梳理
java泛型梳理
概述
- 泛型,即参数化类型,是在JDK1.5之后才开始引入的。
- 所谓参数化类型是指所操作的数据类型在定义时被定义为一个参数,然后在使用时传入具体的类型。
- 这种参数类型可以用在类,接口,方法的创建中,分别被称为泛型类、泛型接口和泛型方法。
- 泛型值存在于java的编译期,编译后生成字节码文件泛型是被擦除的;
Java泛型的底层原理
- 泛型思想最早在C++语言的模板(Templates)中产生,Java后来也借用了这种思想。虽然思想一致,但是他们存在着本质性的不同。
- C++中的模板是真正意义上的泛型,在编译时就将不同模板类型参数编译成对应不同的目标代码,ClassName和ClassName是两种不同的类型,这种泛型被称为真正泛型。这种泛型实现方式,会导致类型膨胀,因为要为不同具体参数生成不同的类。
- Java中ClassName和ClassName虽然在源代码中属于不同的类,但是编译后的字节码中,他们都被替换成原始类型(ClassName),而两者的原始类型的一样的,所以在运行时环境中,ClassName和ClassName就是同一个类。Java中的泛型是一种特殊的语法糖,通过类型擦除实现(后面介绍),这种泛型称为伪泛型。由于Java中有这么一个障眼法,如果没有进行深入研究,就会在产生莫名其妙的问题。值得一提的是,不少大牛对Java的泛型的实现方式很不满意。
- JVM类型擦除
- Java中的泛型是通过类型擦除来实现的。所谓类型擦除,是指通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。
- 实际上,Java的泛型除了类型擦除之外,还会自动生成checkcast指令进行强制类型转换。
泛型解决的问题
// 定义一个List,add()可以存放Object及其子类实例
List list = new ArrayList();
list.add(123); // 合法
list.add("123"); // 合法
// 我们在编译时无法知晓list到底存放的什么数据,于是在进行强制转换时发生异常
int i = (Integer) list.get(1); // 抛出ClassCastException异常
- 上面的代码首先实例化一个ArrayList对象,它可以存放所有Object及其子类实例。分别add一个Integer类型对象和String类型对象,我们原本以为list中存放的全部是Integer类型对象,于是在使用get()方法获取对象后进行强制转换。从代码中可以看到,索引值为1的位置放置的String类型,很显然在进行强制转换时会抛出ClassCastException(类型转换异常)。由于这种异常只会发生在运行时,我们在开发时稍有不慎,就会直接掉到坑里,还很难排查出问题。
- 为什么会出现这种问题呢?
- 集合本身无法对其存放的对象类型进行限定,可以涵盖Java中的所有类型。缺口太大,导致各种蛇、蚁、虫、鼠通通都可以进来。
- 由于我们要使用的实际存放类型的方法,所以不可避免地要进行类型转换。小对象转大对象很容易,大对象转小对象则有很大的风险,因为在编译时,我们无从得知对象真正的类型。
- 泛型就是为了解决这类问题而诞生的
泛型类的定义和使用
- 一个泛型类(generic class)就是具有一个或多个类型变量的类。上面的例子中的List就是一个典型的泛型类。泛型类的定义结构类似下面的代码
- 注意,在Java编码规范中,类型变量通常使用较短的大写字母,并且最好与其作用相匹配。譬如:List中的变量使用E,对应单词Element,Map中的K,V变量对应单词Key和Value。当然这些都是约定性质的东西,其实类型变量的命名规则与Java中的普通变量命名规则是一致的。
public class ClassName<T1, T2> { // 可以任意多个类型变量
public void doSomething(T1 t1) {
System.out.println(t1);
}
}
ClassName<String, String> a = new ClassName<String, String>();
a.doSomething("hello world");
泛型接口的定义和使用
- 接口本质上来说就是一种特殊的类,所以泛型接口的定义和使用与泛型类相差无几。
public interface InterfaceName<T1, T2> { // 可以任意多个类型变量
public void doSomething(T1 t1);
}
public class ConcreteName<T2> implements InterfaceName<String, T2> {
public void doSomething(String t1) {
System.out.println(t1);
}
}
InterfaceName<String, String> a = new ConcreteName<String>();
a.doSomething("hello world");
泛型方法的定义和使用
- 泛型类和泛型接口的类型变量都是定义在类型级别,其作用域可覆盖成员变量和成员方法。泛型方法的类型参数定义在方法签名中.
/**
* 创建一个指定类型的无参构造的对象实例。
* @param <T> 待创建对象的类型。
* @param t 指定类型所对应的Class对象。
* @return 返回创建的对象。
* @throws Exception
*/
public <T> T getObject(Class<T> t) throws Exception {
return t.newInstance();
}
String newStr = generic.getObject(String.class);
泛型变量的类型限定
public <T> T getMax(T t1, T t2) {
if (t1.compareTo(t2) > 1) { // 编译错误
return t1;
} else {
return t2;
}
}
- 在上面的代码无法通过编译,由于我们都没有对类型变量对任何的约束限制,那么实际上这个类型可以是任意Object及其子类。那么在使用这个类型变量时,只能调用Object类中的方法。而Object本身就是Java中对顶层的类,没有实现Comparable接口,所以无法调用compareTo方法来比较对象的大小。这时候可以通过限定类型变量来达到目的。
public <T extends Comparable<T>> T getMax(T t1, T t2) {
if (t1.compareTo(t2) > 1) {
return t1;
} else {
return t2;
}
}
- 注意到上面的代码使用extends关键字限定了类型变量T必须继承自Comparable,于是变量t1和t2就可以使用Comparable接口中的compareTo方法了。
- 不管是泛型类、泛型接口还是泛型方法,都可以进行类型限定。类型限定的特点如下:
- 不管该限定是类还是接口,统一都使用extends关键字。
- 使用&符号进行多个限定,那么传入的具体类型必须同时是这些类型的子类。
- 由于Java中不支持多继承,所以不存在一个同时继承两个以上的类的类。所以,在泛型的限定中,&连接的类型最多只能有一个类,而接口数量则没有限制。同时,如果同时限定类和接口,则必须将类写在最前面。
public <T extends Serializable&Cloneable&Comparable> T getMax(T t1, T t2) {
...
}
public <T extends Object&Serializable&Cloneable&Comparable> T getMax(T t1, T t2) { // 合法
...
}
public <T extends Object&ArrayList> T getMax(T t1, T t2) { // 同时限定两个类,不合法
...
}
public <T extends Serializable&Cloneable&Comparable&Object> T getMax(T t1, T t2) { // 将类写在最后面,不合法
...
}
泛型通配符
- 我们知道Ingeter是Number的一个子类,同时在特性章节中我们也验证过
Generic<Ingeter>
与Generic<Number>
实际上是相同的一种基本类型。那么问题来了,在使用Generic<Number>
作为形参的方法中,能否使用Generic<Ingeter>
的实例传入呢?在逻辑上类似于Generic<Number>
和Generic<Ingeter>
是否可以看成具有父子关系的泛型类型呢?
//为了弄清楚这个问题,我们使用Generic<T>这个泛型类继续看下面的例子:
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);
showKeyValue(gNumber);
// showKeyValue这个方法编译器会为我们报错:Generic<java.lang.Integer>
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);
- 通过提示信息我们可以看到Generic不能被看作为`Generic的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic和Generic父类的引用类型。由此类型通配符应运而生。
public void showKeyValue1(Generic<?> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}
- 类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。
静态方法与泛型
- 静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
public class StaticGenerator<T> {
....
....
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){
}
}
泛型的好处
- 1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3,潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。 - Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
参考
- https://blog.csdn.net/xialei199023/article/details/63251311
- https://www.cnblogs.com/coprince/p/8603492.html
java泛型梳理的更多相关文章
- Java泛型的历史
为什么Java泛型会有当前的缺陷? 之前的章节里已经说明了Java泛型擦除会导致的问题,C++和C#的泛型都是在运行时存在的,难道Java天然不支持“真正的泛型”吗? 事实上,在Java1.5在200 ...
- 浅析Java 泛型
泛型是JavaSE5引入的一个新概念,但是这个概念在编程语言中却是很普遍的一个概念.下面,根据以下内容,我们总结下在Java中使用泛型. 泛型使用的意义 什么是泛型 泛型类 泛型方法 泛型接口 泛型擦 ...
- Java:泛型基础
泛型 引入泛型 传统编写的限制: 在Java中一般的类和方法,只能使用具体的类型,要么是基本数据类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制就会束缚很多! 解决这种限制的 ...
- java泛型基础
泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 这种参数类型可以用在类.接口和方法的创建中, 分别称为泛型类.泛型接口.泛型方法. Ja ...
- 使用java泛型设计通用方法
泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 因此我们可以利用泛型和反射来设计一些通用方法. 现在有2张表, 一张user表和一张stu ...
- 关于Java泛型的使用
在目前我遇到的java项目中,泛型应用的最多的就属集合了.当要从数据库取出多个对象或者说是多条记录时,往往都要使用集合,那么为什么这么使用,或者使用时有什么要注意的地方,请关注以下内容. 感谢Wind ...
- 初识java泛型
1 协变数组类型(covariant array type) 数组的协变性: if A IS-A B then A[] IS-A B[] 也就是说,java中的数组兼容,一个类型的数组兼容他的子类类型 ...
- 【Java心得总结四】Java泛型下——万恶的擦除
一.万恶的擦除 我在自己总结的[Java心得总结三]Java泛型上——初识泛型这篇博文中提到了Java中对泛型擦除的问题,考虑下面代码: import java.util.*; public clas ...
- 【Java心得总结三】Java泛型上——初识泛型
一.函数参数与泛型比较 泛型(generics),从字面的意思理解就是泛化的类型,即参数化类型.泛型的作用是什么,这里与函数参数做一个比较: 无参数的函数: public int[] newIntAr ...
随机推荐
- Qt4.5 QFrame(相当于Delphi里的TPanel,有各种凹凸方式)
QFrame类是有框架的窗口部件的基类. QPopupMenu使用这个来把菜单“升高”,高于周围屏幕.QProgressBar有“凹陷”的外观.QLabel有平坦的外观.这些有框架的窗口部件可以被改变 ...
- 一文带你了解 OAuth2 协议与 Spring Security OAuth2 集成!
OAuth 2.0 允许第三方应用程序访问受限的HTTP资源的授权协议,像平常大家使用Github.Google账号来登陆其他系统时使用的就是 OAuth 2.0 授权框架,下图就是使用Github账 ...
- 20191031-7 beta week 1/2 Scrum立会报告+燃尽图 05
此作业的要求参见:https://edu.cnblogs.com/campus/nenu/2019fall/homework/9915 git地址:https://e.coding.net/Eusti ...
- 【TCP/IP网络编程】:09套接字的多种可选项
本篇文章主要介绍了套接字的几个常用配置选项,包括SO_SNDBUF & SO_RCVBUF.SO_REUSEADDR及TCP_NODELAY等. 套接字可选项和I/O缓冲大小 前文关于套接字的 ...
- 使用ASP.NET Core 3.x 构建 RESTful API - 4.2 过滤和搜索
向Web API传递参数 数据可以通过多种方式来传给API. Binding Source Attributes 会告诉 Model 的绑定引擎从哪里找到绑定源. 共有以下六种 Binding Sou ...
- Java后台创建Socket服务接收硬件终端发送的数据
最近项目中有遇到后台接收硬件终端发送的数据并解析存储的需求,代码总结如下(有时间再来一一讲解,最近比较忙): @Override public void start() { ExecutorServi ...
- Java工程师阅读源码的一些见解
一.为何阅读源码 就是说,通过阅读源码能给你带来什么好处. 学习如何从需求-设计-实现,开阔你的思维,提升你的架构设计能力: 帮助更好地理解原理和架构设计: 帮助更快地定位线上问题BUG 可以根据自己 ...
- linux常用命令,最基础
rmdir keda1/ 6 touch test.java 创建空文件 7 拷贝文件 cp 源文件 目标文件 cp test.java test1.java 8 删除文件rm -r 递归删除 -rf ...
- TensorFlow——学习率衰减的使用方法
在TensorFlow的优化器中, 都要设置学习率.学习率是在精度和速度之间找到一个平衡: 学习率太大,训练的速度会有提升,但是结果的精度不够,而且还可能导致不能收敛出现震荡的情况. 学习率太小,精度 ...
- 2019 ICCV、CVPR、ICLR之视频预测读书笔记
2019 ICCV.CVPR.ICLR之视频预测读书笔记 作者 | 文永亮 学校 | 哈尔滨工业大学(深圳) 研究方向 | 视频预测.时空序列预测 ICCV 2019 CVP github地址:htt ...