深入浅出对JAVA中一个重要的机制--泛型机制的个人理解

内容概述

这篇文章主要是和想大家谈一谈自己对java中泛型相关方面的理解,文章主要从什么是泛型,什么时候用泛型,泛型的作用,如何构建泛型和泛型在各个方面的应用来和大家进行讨论,如果有说的不对的地方希望大家多多包含,不吝赐教。本文会有点长,但是我相信如果你能静心读完,一定会有所收获。我们废话不多说,现在就开始吧。(这里泛型的作用会在说泛型的应用时一并讲到,所以不在单独列出)

java中泛型是什么?

在百度百科中是这么说的,如下图
在这里插入图片描述
在这里我们要清楚,java是一门面向对象的语言,面向对象的一个重要目标是对代码重用的支持。而泛型机制就是支持这个目标的一个重要的机制。如果除去对象的基本类型外,实现方法是相同的,那么我们就可以用泛型实现(generic implementation)来描述这种基本的功能。

泛型的创建

使用Object表示泛型

java中的基本思想就是可以通过是使用像Object这样适当的超类来实现泛型类,如图这个例子。
在这里插入图片描述
当使用这种策略时,有两个细节必须要考虑,第一个细节在下图中表明,字符串"37"用write方法被写入到对象m,然后又从对象m中读出,为了访问这个对象的一个特定方法,必选强制转换成正确的类型。(当然这个例子中可以不不要强制转换,可以在第9行使用toString方法,这种调用对任意对象都是能都做到的)
在这里插入图片描述
第二个重要的细节就是不能使用基本类型。只有引用类型能够与Object相容!这个问题我们稍后会讨论。

基本类型的包装

当我们实现算法的时候,常常遇到语言定型问题:我们已有一种类型的对象,可是语言的语法却需要一种不同类型的对象。在java中,虽然每一个引用类型都和Object相容,但是,8种基本类型却不能。于是java为这8种基本类型中的每一中都提供了一个包装类。如下图体现在第7行,说明如何能够使用MemoryCell来存储整数。
在这里插入图片描述

使用接口类型表示泛型

只有在使用Object类中已有的那些方法能够表示所执行的操作的是时候,才能使用Object作为泛型类型来工作。然而还有这么一种情况。
例如,考虑在由一些项组成的数组中找出最大项的问题。基本的代码是类型无关的,但是它的确需要一种能力来比较任意两个对象,并确定哪个大哪个小。因此,我们不能直接找出Object的数组中的最大元。最简单的方法就是找出Comparable的数组中的最大元,使用compareTo(对所有的Comparable都必然是现成可用的)方法来排序,下图的代码就是就是找出String数组中的最大元。
在这里插入图片描述
在这里有几点需要注意:
第一:只有实现了Comparable接口的那些对象才能够作为Comparable数组的元素被传递。这个程序还告诉我们Circle,square都是Shape的子类。
第二:如果Comparable数组有两个不相容的对象,(例如一个String,一个Shape),那么compareTo方法将抛出异常ClassCastException。这是我们期望的性质。
第三:如前所述,基本类型不能作为Comparable传递,但是包装类则可以,因为他们实现了Comparable接口。
第四:有时宣称一个类实现所需的接口是不可能的。例如让一个库中的类实现我们用户自己定义的一个接口。例如一个类是final类等。

兼容性问题

数组类型的兼容性

语言设计中的困难之一是如何处理集合类型的继承问题。这这里有一个特比好的问题。设Employee IS-A(继承关系) Person。那么,这是不是不是也意味着Employee[] IS-A Person[]呢?换句话说如果一个方法接受Person[]作为参数,那么我们能不能把Employee[]作为参数来传递呢?
乍一看,好像Employee[]就应该是和Person[]类型兼容的,但实际上这个问题要比我们想象中的要复杂。假设除Employee外,我们还有Student IS-A Person,并设Employee[] 和Person[]是类型兼容的。下面大家看一下这两条语句:
在这里插入图片描述
arr[0]实际上是引用一个Employee,可是 Student IS-NOT-A Employee,这样就产生了类型混乱。但是这两句在编译期间是不会报错的。这是因为java中数组是类型兼容的。如果将一个不兼容的类型插入到数组中,那么虚拟机将会抛出一个ArrayStoreException异常。

泛型的应用

简单的泛型类和接口

这里我们把之前描述的MeoryCell类改进成泛型版代码。如下图:
在这里插入图片描述
当指定一个泛型类时,类的声明则包含一个或多个类型参数,这些参数被放在类名后面的一对尖括号内。在这个类声明内部,我们可以声明泛型类型的域和使用泛型类型作为参数或者返回类型的方法。
也可以声明接口是泛型的。例如,在java 5以前,Comparable接口不是泛型的,而它的compareTo方法需要一个Object作为参数。于是,传递到compareTo方法的任何引用变量即使不是一个合理的类型也都会编译,而只是在运行是报告ClassCastException错误。在java 5之后,Comparable接口是泛型的,如下图所示,现在String类实现Comparable并有一个compareTo方法,这个方法以String作为其参数。这样,通过使类变成泛型类,以前只在运行时才报告的许多错误如今变成了在编译时的错误了。

自动装箱/拆箱

像之前图中代码那样,使用包装类需要在write之前创建Integer对象,然后才能使用intValue方法从Integer中提取int值,在java后,就不用这么麻烦了,如果一个int型变量被传递到一个需要integer对象的地方,那么编译器会在幕后插入一个对Integer构造方法的调用,这就是自动装箱,反之则是自动拆箱。如下图:
在这里插入图片描述
要注意的是GenericMemoryCell中引用的那些实体仍然是Integer对象。在类GenericMemoryCell的实例化中,int不能代替Integer。

带有限制的通配符

如下图1显示了一个static方法,用于计算一个Shape数组的总面积。假设我们想要重写这个计算总面积的方法,使得该方法能够使用Collection这样的参数。(Collection能存储一些项)
在这里插入图片描述
这里就会出现一个需要注意的问题,那就是如果传递的一个Collection程序会正常运行,那么传递一个Collection会发生什么情况?(注:square继承Shape类)。答案依赖于是否Collection IS-A Collection,用技术术语来说就是是否我们拥有协变性。
我们之前提到java中的数组是协变的。于是,Square[] IS-A Shape[]。一方面,这种一致性意味着,如果数组是协变的,那么集合也将是协变的。另一方面,数组的协变性导致代码得以编译,但此后会产生一个运行时异常(ArrayStoreException)。因为使用泛型的全部原因就在于产生编译器错误而不是类型不匹配的运行时异常,所以,泛型集合不是协变的。因此,我们不能把Collection作为参数传递到上图中,这点很重要。
现在的问题是,泛型(以及泛型集合)不是协变的(但有意义),而数组是协变的。若无附加的语法,则用户就会避免使用集合(collection),因为失去协变性使得代码缺少灵活性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值