Java 泛型(1):基本原理

1.1      擦除

15.7例子

Java的泛型是同伙擦除来实现的,在泛型代码内部,无法获得任何有关泛型参数类型的信息(这一点有别于C++等实现),这意味着你在使用泛型时,无法知道任何类型信息,只知道你在使用一个对象,因此List<String>和List<Integer>在运行时事实上是相同的类型。

1.1.1        C++的实现

15.7.1例子

C++的实现与Java实现不同,当你实例化模板时,C++编译器会进行检查,模板代码知道参数类型,所以如果调用了类型的某些方法,将由编译器在编译时进行检查

 

1.1.2        为何采用擦除

泛型不是Java出现时就有的组成部分,而是中途加入的,为了向下兼容许多现有的代码和类文件,并且保持其原有的含义,所以采用了擦除这一解决方案,允许非泛型和泛型代码共存。

       必须意识到,擦除并不是实现泛型最好的技术,如果泛型出现在Java 1.0,那么将不会使用擦除来实现,而是使用具体化,这样你就可以在类型参数上执行基于类型的操作和反射,擦除减少了泛型的泛化性,使得Java的泛型没有本来设想的那么有用,但是泛型在Java中仍然是一个很有用的特性。

1.1.3        擦除引入的问题

由于擦除的原因,Java的泛型不能用于显示地引用运行时类型的操作,例如转型、instanceof、new等操作。因为所有的参数类型都丢失了,在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有了参数类型信息,例如:

Foo<Cat> f = new Foo<Cat>();

我们通常会认为,Foo的代码应该知道操作的对象是Cat,而且从语法上也给人这样的暗示:在Foo类中,所有的地方类型T都被替换成了Cat。但是事实却完全不是这样,你必须要知道,它只是一个Object。

 

 

1.2      边界

边界就是在泛型的参数类型上设置限制条件,比如<T extends Shape>等,使用边界让你可以强制规定泛型可以应用的类型。

    因为擦除了类型信息,导致我们只能调用那些Object的方法,但是,我们可以通过将这个参数限制为某个类型的子集,那么你就可以用这些类型子集的方法。我们可以通过继承来消除边界书写上的冗余,如下例所示,通过继承,在每个层次上添加边界限制,在不断的继承中,T拥有了越来越多的成员。

public interface IGetName {public String getName();}public interface Weight {public int getWeight();}public class Shape {int x;int y;int z;}public class Cat<T> {T item;Cat(T item) {this.item = item;}}public class MyCat<T extends IGetName> extends Cat<T>{MyCat(T item) {super(item);}public String getMyCatName() {return item.getName();}}public class MyCats<T extends Shape & IGetName & Weight> extends MyCat<T> {MyCats(T item) {super(item);// TODO Auto-generated constructor stub
}int getCatWeight() {return item.getWeight();}int getShape() {return item.x + item.y + item.z;}}

 

 

注意:在继承中,T的作用域必须越来越窄,不能越来越宽,例如:IgetName接口在MyCat的类中已经作了限制,如果在MyCats的继承中将IGetName去掉(class MyCats<T extends Shape & Weight> extends MyCat<T>),将会出现The type T is not a valid substitute for the bounded parameter <T extends IGetName> of the type MyCat<T>的错误,原因就是T的作用域变宽了(父类的T必须是继承IGetName,而子类没有这一限制)。

    在类型边界限制中,不管是类还是接口都是使用extends,中间使用&分隔,同时需要注意的时,如果有类继承,那么类必须写在第一个,接口写在第二个到第n个,<T extends 类 & 接口1 & 接口2 & 接口3 ...>。

1.3      泛型数组

1.3.1        为什么Java不能创建泛型数组

擦除会移除参数类型信息,而数组必须知道他们所持有的确切类型,以强制保障类型安全

在Java中,Object[]数组可以是任何数组的父类,或者说,任何一个数组都可以向上转型成它在定义时指定元素类型的父类的数组,这个时候如果我们往里面放不同于原始数据类型 但是满足后来使用的父类类型的话,编译不会有问题,但是在运行时会检查加入数组的对象的类型,于是会抛ArrayStoreException:

String[]strArray=newString[20];Object[]objArray=strArray;objArray[0]=newInteger(1);// throws ArrayStoreException at runtime

 

因为Java的范型会在编译后将类型信息抹掉,这样如果Java允许我们使用类似Map<Integer,String>[]mapArray=newMap<Integer,String>[20];这样的语句的话,我们在随后的代码中可以把它转型为Object[]然后往里面放Map<Double, String>实例。这样做不但编译器不能发现类型错误,就连运行时的数组存储检查对它也无能为力,它能看到的是我们往里面放Map的对象,我们定义的<Integer, String>在这个时候已经被抹掉了,于是而对它而言,只要是Map,都是合法的。想想看,我们本来定义的是装Map<Integer, String>的数组,结果我们却可以往里面放任何Map,接下来如果有代码试图按原有的定义去取值,后果是什么不言自明。

       如上原因,Java中不能创建泛型数组,一般的解决方案是在你想创建泛型数组的地方使用ArrayList。

1.3.2        生成泛型数组的折中方法

       有趣的是,虽然Java中不能创建泛型数组,但是你却可以定义一个泛型数组的引用,而且编译器不会产生任何警告,但是你却永远不能创建这个数组。

Foo<Cat> []f = new Foo<Cat>[SIZE];        //error

Foo<Cat> [f];                                    //right

       既然无法创建泛型数组,那么泛型数组的引用拿来干什么呢,考虑以下用法:

              Foo<Cat> []f = (Foo<Cat>)new Object[SIZE];  //compile ok, run error

数组的每个元素应该持有相同的类型,那么通过创建一个Object的数组进行强转成泛型数组是否可行呢?事实上以上代码是可以编译的,但是当你运行的时候,却会抛出ClassCastException异常。原因是虽然我们将Object进行了强转(所以编译期不会报错),但是在运行时它仍然是Object,所以引发了类异常。

 

事实上,生成泛型数组的唯一方式是创建一个被擦除类型的新数组,然后对其转型,例如: T [] array;  array = (T[])new Object[SIZE]; 考虑以下例子,我们使用泛型数组来生成一个自己的简单的数组:

class MyArray<T> {private T[] array;public MyArray(int size) {array = (T[])new Object[size];}public void set(T item, int n) {array[n] = item;}public T get(int n) {return array[n];}public T[] getArray( ) {return array;}public static void prt(String str) {System.out.println(str);}public static void main(String []args) {MyArray<String> my = new MyArray(3);my.set("a", 0);my.set("b", 1);my.set("c", 2);prt(my.get(1));//String[] o = my.getArray();   //run error
Object[] o = my.getArray();prt(Arrays.toString(o));}}//out

b[a, b, c]

 

 

注意:我们仍然不能使用T[] array = new T[SIZE];只能使用创建对象数组,然后强转的方式,因为Object是任何类型的基类,所以我们可以将String类型的对象赋值到数组上,注意方法getArray(),它的返回值为T[],在main函数中,声明的是<String>,所以我们尝试将其赋予到String[]数组上,但是此时运行时发生了类型错误,这是因为本质上我们的数组还是Object[],所以在运行中试图将一个Object[]赋值到String[],自然会发生异常。

       事实上,更加稳妥的做法是将上述代码中的数组声明,更改成Object[] array,由于Java的擦除技术,所以我们运行的时候类型都会是Object,如果我们立刻将其转型成T[],那么我们编译期就会丢失数组的实际类型(Object),此时编译器可能错过一些潜在的错误检查。另外一点,使用Object[]声明可以随时提醒我们,这个数组运行时是Object,而不是T[]。

 

1.3.3        生成“真正的”泛型数组

仍然考虑上述的例子,如果我们将参数类型传入进去,那么我们可以生成一个“真正的”泛型数组,通过反射技术,生成了实际传入的参数类型(String)的数组,这样array的实际类型就是String[],而不是上例的Object[],所以在main()中getArray()直接返回给String[]类型的引用没有任何问题。

class MyArray<T> {private T[] array;public MyArray(Class<T> type, int size) {array = (T[])Array.newInstance(type, size);}public void set(T item, int n) {array[n] = item;}public T[] getArray( ) {return array;}public static void main(String []args) {MyArray<String> my = new MyArray(String.class, 3);my.set("a", 0);my.set("b", 1);my.set("c", 2);String[] o = my.getArray();System.out.println(Arrays.toString(o));}}

 

 

转载于:https://www.cnblogs.com/LemonPi/p/11040252.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/569308.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

21-MySQL-Ubuntu-快速回到SQL语句的行首和行末

行首: Ctrl A 行末:Ctrl E转载于:https://www.cnblogs.com/summer1019/p/11043692.html

django CBV装饰器 自定义django中间件 csrf跨站请求伪造 auth认证模块

CBV加装饰器 第一种  method_decorator(装饰器&#xff09;  加在get上 第二种  method_decorator(login_auth,nameget)  加在类上 第三种  method_decorator(login_auth)  加在dispatch上  3.7的要return super().dispatch def login(request):if request.metho…

Dubbo理论知识

本文是作者根据官方文档以及自己平时的使用情况&#xff0c;对 Dubbo 所做的一个总结。如果不懂 Dubbo 的使用的话&#xff0c;可以参考我的这篇文章《超详细&#xff0c;新手都能看懂 &#xff01;使用SpringBootDubbo 搭建一个简单的分布式服务》 Dubbo 官网&#xff1a;http…

外观模式(facade)

外观模式是为了解决类与类之间的依赖关系的&#xff0c;像spring一样&#xff0c;可以将类和类之间的关系配置到配置文件中&#xff0c;而外观模式就是将他们的关系放在一个Facade类中&#xff0c;降低了类类之间的耦合度&#xff0c;该模式中没有涉及到接口&#xff0c;看下类…

套接字TCP控制台程序客户端代码示范

套接字TCP控制台程序客户端代码示范 转载于:https://www.cnblogs.com/txwtech/p/11056770.html

widows下nignx的使用

nignx在Linux环境下可以大展身手&#xff0c;在widows环境下也可以启动一定的效果&#xff0c;但是没有linux用的好。 Nginx (engine x) 是一款轻量级的Web 服务器 、反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。 什么是反向代理&#xff1f; 反向代…

享元模式(Flyweight)

享元模式的主要目的是实现对象的共享&#xff0c;即共享池&#xff0c;当系统中对象多的时候可以减少内存的开销&#xff0c;通常与工厂模式一起使用。 FlyWeightFactory负责创建和管理享元单元&#xff0c;当一个客户端请求时&#xff0c;工厂需要检查当前对象池中是否有符合条…

redo

在innodb存储引擎中&#xff0c;事务日志通过重做(redo)日志文件和InnoDB存储引擎的日志缓冲(InnoDB Log Buffer)来实现。当开始一个事务时&#xff0c;会记录该事务的一个LSN(Log Sequence Number&#xff0c;日志序列号)&#xff1b;当事务执行时&#xff0c;会往InnoDB存储引…

迭代子模式(Iterator)

顾名思义&#xff0c;迭代器模式就是顺序访问聚集中的对象&#xff0c;一般来说&#xff0c;集合中非常常见&#xff0c;如果对集合类比较熟悉的话&#xff0c;理解本模式会十分轻松。这句话包含两层意思&#xff1a;一是需要遍历的对象&#xff0c;即聚集对象&#xff0c;二是…

ES6基础之——对象表达式

这里定义了两个变量&#xff0c;一个是dessert&#xff0c;一个是drink,let dessertcake,drinkorange;一、根据两个变量的值去定义一个对象&#xff0c;这个对象叫food,对象里面属性跟变量的名字是一样的,属性值对应上面的变量let dessertcake,drinkorange; let food{ dessert:…

前台获取元素节点

$("#id").childNodes;//获取id的全部子节点&#xff1b; $("#id").parentNode;//获取id的父节点&#xff1b; $("#id").nextSibling;//获取id的下一个兄弟节点 $("#id").previousSibling;//获取id的上一个兄弟节点 $("#id")…

oracle查看执行计划入门

基于Oracle的应用系统很多的性能问题都是由应用系统的SQL性能低劣引起的&#xff0c;因此SQL的性能优化非常重要。要分析与优化SQL的性能&#xff0c;一般是通过查看该SQL的执行计划&#xff0c;然后通过执行计划有针对性地对SQL进行相应的优化。 什么是执行计划&#xff08;Ex…

这半年……

有半日也还清闲的时间&#xff0c;一年的一半也快过完了&#xff0c;那就来谈一谈这半年吧&#xff0c;也对下半年提出点期待。 年初&#xff0c;提出了本年度的的关键词&#xff1a;真实、踏实、勤快。 基于这个指导思想&#xff0c;我对自己半年的评价是&#xff1a;做得不错…

部署自己的tomcat,让tomcat和IIS共同享用服务器的80端口

这几天做了一个网站的CMS系统,当然就要用到TOMCAT来对项目进行部署了.但是客户的服务器上已经安转了我们用.NET制作的OA系统&#xff0c;所以在客户的服务器上已经有完整的IIS7服务&#xff0c;而IIS7默认会占用所有的80端口&#xff08;虽然服务器有两个IP&#xff0c;而它只用…

ueditor1.4.3配置过程(包含单独上传文件以及图片的使用)

这里使用的是ueditor1.4.3的jsp版本的UTF-8版本. 首先下载相应的ueditor,将ueditor文件夹直接拷贝到项目中,文件结构如下所示: 然后将项目要用的jar包导入到lib目录下,在导入之前要先修改一下ueditor的jar包中的bug,这个可以参考这里点击打开链接 接着就是配置jsp目录下的con…

layui如何隐藏弹出层关闭的按钮

layui默认弹出层是带有关闭按钮的&#xff0c;但是在某些场景我们不需要layui的关闭按钮&#xff0c;这时只需添加closeBtn :0即可效果图如下: 示例代码如下: layui.use(layer, function () {var layer layui.layer;layer.open({skin: demo-class,type: 1,title: 登录,area: […

使用httpclient4.3.2来实现微信临时素材的上传

一直在用java来做微信的二次开发&#xff0c;经过一段时间的沉淀总算有了一点门路。其实用java这种强大的语言来做微信的二次开发是很简单的事情。只要解决了加密、https请求的发送、xml的解析这些基本的操作后&#xff0c;用java来进行微信二次开发就变的容易了很多。这里我主…

charles请求入参中有乱码

工作中&#xff0c;需要入参&#xff0c;但是发现入参中&#xff0c;有中文的都是乱码&#xff0c;仔细查阅headers&#xff0c;发现Content-Type是application/x-www-form-urlencoded类型&#xff0c;而实际上&#xff0c;入参是json类型&#xff0c;因此需要强制修改请求头为…

xstream,节点属性起别名时这样的问题你遇到过吗

首先这是我自己定义的一个xstream&#xff0c;这个xstream是为了在处理xml时能够加上<![CDATA[------]]>而特别重写的。这个xstream是没有任何问题的。 private static XStream xstream new XStream(new XppDriver() {public HierarchicalStreamWriter createWriter(Wri…

20190624 Oracle 表分析

dbms_stats.gather_table_stats&#xff08;。。。&#xff09; 参数要注意 正常情况只是为了分析表&#xff0c;也可以通过方法创建记录表 分析的结果会记录的记录表中&#xff0c;当然每次运行会有覆盖&#xff0c;主要分析后的结果。 分析表&#xff0c;记录了目前此表的情况…