Java基础:泛型及其擦除性、不可协变性
转载请注明出处:jiq•钦's
technical Blog
1泛型语法:
泛型类: class ClassName<T>{}
泛型方法:public <T> void f(T x){}
基本指导原则:假设使用泛型方法能够代替将整个类泛型化,那么就应该使用泛型方法,由于它能够让事情更加清楚。
2为什么使用泛型?
在Java SE1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现參数的“随意化”,“随意化”带来的缺点是要做显式的强制类型转换,而这样的转换是要求开发人员对实际參数类型能够预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在执行的时候才出现异常。这是一个安全隐患。
泛型的优点是:
(1)在编译时检查类型安全。
(2)而且全部的强制转换都是自己主动和隐式的,提高代码的重用率。
促成泛型出现最引人注目的一个原因就是为了创造容器类。
优先考虑泛型!!!
使用泛型比使用须要在client代码中进行转换的类型来的更加安全,也更加easy。
在设计新类型的时候,要确保它们不须要这样的转换就能够使用。这通常意味着要把类做成是泛型的。
比方
client代码使用Stack的时候,假设不使用泛型,在取用Stack中的对象时还须要进行不安全的类型推断。类型转换等,并且代码还比較复杂。假设使用带泛型的Stack,client代码中就不须要进行类型转换了,直接使用并且不会复杂。
3泛型数组:
无法创建泛型数组,一般的解决方案是在不论什么想要使用泛型数组的地方使用ArrayList。
public <T> voidtest()
{
//Cannotcreate a generic array of T
T[]tList = new T[10];
int[]iList = new int[10]; //useArrayList<T>
ArrayList<T>sList = new ArrayList<T>();
}
4类型擦除:
一、概念
类型擦除:将泛型类型中类型參数信息去除,仅仅映射为一份字节码。在必要时进行类型检查和类型转换。
编译时通过两个步骤来对泛型类型的类型进行擦除:
1.将全部的泛型參数用其最左边界(最顶级的父类型)类型替换。
2.移除全部的类型參数。
备注:擦除也是为什么泛型必须编译时进行类型检查的原因。由于执行时类型信息被擦除了。
二、擦除举例
(1)容器泛型类型擦除:
ArrayList<Integer>l1 = new ArrayList<Integer>();
ArrayList<String> l2= new ArrayList<String>();
LinkedList<Integer>l3 = new LinkedList<Integer>();
List<String> l4 =new LinkedList<String>(); System.out.println(l1.getClass().getName());
System.out.println(l2.getClass().getName());
System.out.println(l3.getClass().getName());
System.out.println(l4.getClass().getName()); //output
java.util.ArrayList
java.util.ArrayList
java.util.LinkedList
java.util.LinkedList
能够看到上面四个泛型类型的类型信息均被擦出掉了,比方ArrayList<Integer>和ArrayList<String>编译后生成的字节码是一份。
(2)自己定义泛型类型擦除:
再看一个自己定义的泛型类:
class TObject<T>
{
privateT obj;
publicvoid Set(T object)
{
this.obj= object;
System.out.println("T:"+object.getClass().getName());
}
}
编译擦除后,生成的类是这样:
class TObject
{
privateObject obj;
publicvoid Set(Object object)
{
this.obj= object;
}
}
首先泛型參数T被向上替换为自身的顶级父类Object,然后将类型參数T去除。
(3)自己定义继承关系泛型类型擦除:
class Manipulator<Textends SuperClass>
{ private T obj;
public Manipulator(T x){
obj = x;
}
public void doSomething(){
obj.f();
System.out.println(obj.getClass().getName());
}
}
首先将泛型參数T向上替换为上边界。然后去除泛型參数:
class Manipulator
{
private SuperClass obj;
public Manipulator(SuperClass x){
obj = x;
}
public void doSomething(){
obj.f();
System.out.println(obj.getClass().getName());
}
}
三、擦除原因:
泛型不是在java一開始就有的。擦除是java中泛型实现的一种折中手段。
详细来说两个原因使得泛型代码须要类型擦除:
(1)引入泛型代码不能对现有代码类库产生影响,所以须要将泛型代码擦除为非泛型代码;
(2)当泛型代码被当做类库使用时。为了兼容性,不须要知道泛型代码是否使用泛型,所以须要擦除;
5不可协变:
(1)数组和泛型对照
数组是可协变的、泛型是不可协变的。
什么是可协变性?举个样例说明:
数组可协变(covariant)是指假设类Base是类Sub的基类。那么Base[]就是Sub[]的基类。
泛型不可变的(invariant)是指List<Base>不会是List<Sub>的基类,两者压根没关系。
(2)泛型为什么不可协变
泛型“编译时进行类型检查(类型安全)”特性决定了其不可协变。
ArrayList<Object> objList = new ArrayList<Long>(); |
//can't compile pass |
类型安全检查 |
Object[] objArray = new Long[10]; |
//compile OK |
假设ArrayList<Long>类型对象能够赋值给ArrayList<Object>类型引用,那么就违反了泛型类型安全的原则。
由于假设编译器你这样做,会导致能够往容器中放置非Long型的对象。
可是数组就无所谓,他不是类型安全的。
再看看以下代码。第一行编译错误是由于不可协变性,那么为什么第二行能够呢?
List<Type> listt = new ArrayList<SubType>(); |
//can't compile pass |
|
List<? extends Type> listt = new ArrayList<SubType>(); |
//OK |
參考泛型通配符,这就是其作用 |
不可协变并不代表不能在泛型代码中将父类出现的地方使用子类取代,如以下代码是合法的:
ArrayList<Type> list = new ArrayList<Type>(); |
//Type is SuperClass |
list.add(new SubType()); |
//SubType is SubClass |
Type[] tt = new Type[3]; |
tt[0] = new SubType(); |
(3)数组可协变带来的问题:
数组的协变性可能会导致一些错误,比方以下的代码:
public static voidmain(String[] args) {
Object[] array = new String[10];
array[0] = 10;
}
它是能够编译通过的,由于数组是协变的,Object[]类型的引用能够指向一个String[]类型的对象。可是执行的时候是会报出例如以下异常的:
Exception in thread"main" java.lang.ArrayStoreException: java.lang.Integer
可是对于泛型就不会出现这样的情况了:
public static voidmain(String[] args) {
List< Object> list = newArrayList< String>();
list.add(10);
}
这段代码连编译都不能通过。
6通配符:
通配符在类型系统中的作用部分来自其不会发生协变(covariant)这一特性。即通配符产生一部分原因来自突破不可协变的限制。
能够觉得通配符使得List<?>是List<AnyType>的基类。List<? extends Type>是List<SubType>的基类。
// collection1能够存放不论什么类型
Collection<? >collection1 = new ArrayList<String>();
collection1 = newArrayList<Integer>();
collection1 = newArrayList<Object>(); //collection3表示它能够存放Number或Number的子类
Collection<?extends Number> collection3 = null;
collection3 = newArrayList<Number>();
collection3 = newArrayList<Double>();
collection3 = newArrayList<Long>(); //collection4表示它能够存放Integer或Integer的父类
Collection<? superInteger> collection4 = null;
collection4 = newArrayList<Object>();
Java基础:泛型及其擦除性、不可协变性的更多相关文章
- 一天一个Java基础——泛型
这学期的新课——设计模式,由我仰慕已久的老师传授,可惜思维过快,第一节就被老师挑中上去敲代码,自此在心里烙下了阴影,都是Java基础欠下的债 这学期的新课——算法设计与分析,虽老师不爱与同学互动式的讲 ...
- Java中泛型 类型擦除
转自:Java中泛型是类型擦除的 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类 ...
- Java 基础 -- 泛型、集合、IO、反射
package com.java.map.test; import java.util.ArrayList; import java.util.Collection; import java.util ...
- java基础-泛型举例详解
泛型 泛型是JDK5.0增加的新特性,泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数.这种类型参数可以在类.接口.和方法的创建中,分别被称为泛型类.泛型接口.泛型方法. 一.认识泛型 在没 ...
- Java基础 - 泛型详解
2022-03-24 09:55:06 @GhostFace 泛型 什么是泛型? 来自博客 Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了&quo ...
- java基础-泛型3
浏览以下内容前,请点击并阅读 声明 8 类型擦除 为实现泛型,java编译器进行如下操作进行类型擦除: 如果类型参数有限制则替换为限制的类型,如果没有则替换为Object类,变成普通的类,接口和方法. ...
- java基础 泛型
泛型的存在,是为了使用不确定的类型. 为什么有泛型? 1. 为了提高安全 2. 提高代码的重用率 (自动 装箱,拆箱功能) 一切好处看代码: package test1; import java.la ...
- java基础-泛型2
浏览以下内容前,请点击并阅读 声明 6 类型推测 java编译器能够检查所有的方法调用和对应的声明来决定类型的实参,即类型推测,类型的推测算法推测满足所有参数的最具体类型,如下例所示: //泛型方法的 ...
- java基础-泛型1
浏览以下内容前,请点击并阅读 声明 泛型的使用能使类型名称作为类或者接口定义中的参数,就像一般的参数一样,使得定义的类型通用性更强. 泛型的优势: 编译具有严格的类型检查 java编译器对于泛型代码的 ...
随机推荐
- 互联网创业十问?good(快速迭代、把握核心用户应对抄袭,不需要把商业模式考虑完备,4种失败的信号,失败者没资格说趁着年轻...)
著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处.作者:曹政链接:https://www.zhihu.com/question/20264499/answer/28168079来源: ...
- 转:什么是 HTTP Headers?
什么是HTTP Headers HTTP是“Hypertext Transfer Protocol”的所写,整个万维网都在使用这种协议,几乎你在浏览器里看到的大部分内容都是通过http协议来传输的,比 ...
- java的new BufferedReader(new InputStreamReader(System.in))
流 JAVA /IO 基本小结 通过一行常见的代码讨论:new BufferedReader(new InputStreamReader(System.in)) /*** *** 看到这篇文章挺好的, ...
- “快的打车”创始人陈伟星的新项目招人啦,高薪急招Java服务端/Android/Ios 客户端研发工程师/ mysql DBA/ app市场推广专家,欢迎大家加入我们的团队! - V2EX
"快的打车"创始人陈伟星的新项目招人啦,高薪急招Java服务端/Android/Ios 客户端研发工程师/ mysql DBA/ app市场推广专家,欢迎大家加入我们的团队! - ...
- visual c++ 2010安装未成功
可能是已经安装了其他版本的Microsoft visual studio 参考: http://answers.microsoft.com/zh-hans/windows/forum/windows_ ...
- 二维码的妙用:通过Zxing实现wifi账号password分享功能
二维码是搭载信息的一种载体,通过二维码能够传递名片.网址.商品信息等,本文讲到二维码的第二种妙用:通过二维码实现wifi账号和password分享. 关于二维码的基础知识,请訪问:二维码的生成细节和原 ...
- iOS WebCore的WebEvent和EventHandler
WebEvent是iOS专有的类,负责封装和携带从UIKit得到的系统事件信息,并由WebKit层的WAKResponder子类传递到WebCore的EventHandler. UIKit层的逻辑可参 ...
- 判断程序是否在VMWare内运行
现在有许多用户都喜欢用虚拟机来测试他们的软件,以避免对真实机器环境造成损害.但是在虚拟机中,有些功能是受限,甚至不可能完成的,因此,需要在程序中判断虚拟机的环境,如果程序在虚拟机内运行,则就要把虚拟机 ...
- 盘点:#AzureChat - 虚拟机和自动伸缩
感谢大家跟 Corey Sanders 和 Stephen Siciliano 一起参加本次 #AzureChat.我们很高兴能借这次在线讨论的机会,倾听各位社区成员对我们最受欢迎的两个主题的意见 - ...
- android 高效显示Bitmap - 开发文档翻译
由于本人英文能力实在有限,不足之初敬请谅解 本博客只要没有注明“转”,那么均为原创,转贴请注明本博客链接链接 Displaying Bitmaps Efficiently 高效显示Bitmap Lea ...