<数据结构与算法分析>读书笔记--利用Java5泛型实现泛型构件
一、简单的泛型类和接口
当指定一个泛型类时,类的声明则包括一个或多个类型参数,这些参数被放入在类名后面的一对尖括号内。
示例一:
package cn.generic.example; public class GenericMemoryCell <AnyType>{ public AnyType read() { return storedValue;
} public void write(AnyType x) { storedValue=x;
} private AnyType storedValue; }
GenericMemoryCell有一个类型参数。在这个例子中对类型参数没有明显的限制,所以用户可以创建像GenericMemoryCell<String>和GenericMemoryCell<Integer>类声明内部,我们可以声明泛型类型的域和使用泛型类型作为参数或返回类型的方法。比如,类GenericMemoryCell<String>的write方法需要一个String类型的参数。如果传递其它参数那将产生一个编译错误。
同时也可以声明接口是泛型的。
示例二:
package cn.generic.example; public interface Comparable <AnyType>{ public int compareTo(AnyType other); }
在Java5以前,Comparable接口不是泛型,而它的comparaTo()方法需要一个Object作为参数。于是,传递到compareTo方法的任何引用变量即使不是一个合理的类型也都会编译,而只是在运行时报告ClassCastException错误。在Java5中Comparable接口是泛型的。
再比如以我目前用到的ORM框架MyBatis-Plus,其中的BaseMapper也是泛型接口,如下图所示:
二、自动装箱和拆箱
什么是装箱和拆箱?
一句话概括:装箱就是自动将基本数据类型转换为包装器类型;拆箱就是 自动将包装器类型转换为基本数据类型。
示例三(可与<数据结构与算法分析>读书笔记--实现泛型构件pre-Java5 中的示例三代码进行比较):
package cn.generic.example; public class BoxingDemo { public static void main(String[] args) { GenericMemoryCell<Integer> m = new GenericMemoryCell<Integer>(); m.write(37); int val = m.read(); System.out.println("Contents are:"+val);
}
}
三、菱形运算符
以上面的示例三代码中的GenericMemoryCell<Integer> m = new GenericMemoryCell<Integer>()来说,有些烦人,因为既然m是GenericMemoryCell<Integer>类型的,显然创建的对象也必须是GenericMemoryCell<Integer>类型的,任何其他类型的参数都会产生编译错误。Java7增加了一种新的语言特征,称为菱形运算符。
可以将GenericMemoryCell<Integer> m = new GenericMemoryCell<Integer>()改写为GenericMemoryCell<Integer> m = new GenericMemoryCell<>()
示例四:
package cn.generic.example; public class BoxingDemo { public static void main(String[] args) { GenericMemoryCell<Integer> m = new GenericMemoryCell<>();
m.write(5);
int val = m.read();
System.out.println("Contents are:"+val);
}
}
四、带有限制的通配符
带限制的通配符,通常有两种表现形式:
? extends E
? super E
使用原则可遵循PECS原则,其实就是四个单词的组合。
PECS — producer-extends, consumer-super
翻译过来就是生产者继承,消费者使用。
五、泛型static方法
有时候特定类型很重要,或许是因为下面几个原因:
(1)特定类型用作返回类型;
(2)该类型用在多于一个的参数类型中;
(3)该类型用于声明一个局部变量。
如果是这样,那么,必须要声明一种带很多类型参数的显式泛型方法。
示例五:
package cn.generic.example; public class GenericStaticExample { public static <AnyType> boolean contains(AnyType[]arr, AnyType x) { for(AnyType val:arr) if(x.equals(val)) return true; return false; } }
上面显示是一种泛型static方法,该方法对值x在数组arr中进行一系列查找。通过使用一种泛型方法,代替使用Object作为参数的非泛型方法,当在Shape对象的数组中查找Apple对象时我们能够得到编译时错误。
泛型方法特别像是泛型类,因为类型参数表使用相同的语法。在泛型方法中的类型参数位于返回类型之前。
六、类型限界
示例六(在一个数组中找出最大元的泛型static方法,以例说明类型参数的限界)
package cn.generic.example; public class TypeLimitExample { public static <AnyType extends Comparable<? super AnyType>> AnyType findMax(AnyType[] arr) { int maxIndex = 0; for (int i = 0; i < arr.length; i++) if(arr[i].compareTo(arr[maxIndex])>0)
maxIndex = i; return arr[maxIndex]; } }
七、类型擦除
泛型在很大程度上是Java语言中的成分而不是虚拟机中的结构。泛型类可以由编译器通过所谓的类型擦除过程而转为非泛型类。这样,编译器就生成一种与泛型类同名的原始类,但是类型参数都被删去了。类型变量由它们的类型限界来代替,当一个具有擦除返回类型的泛型方法被调用时,一些特性被自动插入。如果使用一个泛型类而不带泛型参数,那么使用的是原始类。
类型擦除的一个重要推论是,所生成的代码与程序员在泛型之前所写的代码并没有太多的差异,而且事实上运行的也并不快。其显著优点在于,程序员不必把一些类型转换放到代码中,编译器将进行重要的类型检验。
八、对于泛型的限制
对于泛型类型有许多限制。由于类型擦除的原因,这里列出的每一个限制都是必须要遵守的。
1.基本类型
基本类型不能用做类型参数。因此,GenericMemoryCell<int>是非法的。我们必须要使用包装类。
2.instanceof检测
instanceof检测和类型转换工作只对原始类型进行。
示例七:
package cn.generic.example; public class InstanceOfCheckExample { public static void main(String[] args) {
GenericMemoryCell<Integer> celll = new GenericMemoryCell<>();
celll.write(4); Object cell = celll; GenericMemoryCell<String> cell2 = (GenericMemoryCell<String>) cell;
String s = cell2.read(); } }
这里的类型转换在运行时是成功的,因为所有的类型都是GenericMemoryCell。但在最后一行,由于对read的调用企图返回一个String对象从而产生一个运行时错误。
结果,类型转换将产生一个警告,而对应的instanceof检测是非法的。
3.static的语境
在一个泛型类中,static方法和static域均不可引用类的类型变量,因为在类型擦除后类型变量就不存在了。另外,由于实际上只存在一个原始类,因此static域在该类的诸泛型实例之间是共享的。
4.泛型类型实例化
不能创建一个泛型类型的实例。如果T是一个类型变量
T obj = new T();
则语句是非法的。T由它的限界代替,这可能是Object或抽象类,因此对new的调用没有意义。
5.泛型数组对象
也不能创建一个泛型数组。如果T是一个类型变量
T[] arr = new T[10];
则语句是非法的。T将由它的限界代替,这可能是Object T,于是(由类型擦除产生的)对T[]的类型转换将无法进行,因为Object[] IS-NOT-A T[]。由于我们不能创建泛型对象的数组,因此一般说来我们必须创建一个擦除类型的数组,然后使用类型转换。这种类型转换将产生一个关于未检验的类型转换的编译警告。
6.参数化类型的数组
参数化类型的数组的实例化是非法的。
GenericMemoryCell<String> [] arr1 = new GenericMemoryCell<>[10];
GenericMemoryCell<Double> cell = new GenericMemoryCell<>();
cell.write(4.5);
Object[] arr2 = arr1;
arr2[0] = cell;
String s = arr1[0].read();
正常情况下,我们认为第四行的赋值会生成一个ArrayStoreException,因为赋值的类型有错误。可是,在类型擦除之后,数组的类型为GenericMemoryCell[],而加到数组中的对象也是GenericMemoryCell,因此不存在ArrayStoreException异常。于是,该段代码没有类型转换,它最终将在第五行产生一个ClassCastException异常,这正是泛型应该避免的情况。
示例代码已经上传到我的Github:https://github.com/youcong1996/The-Data-structures-and-algorithms/tree/master/Introduction
<数据结构与算法分析>读书笔记--利用Java5泛型实现泛型构件的更多相关文章
- <数据结构与算法分析>读书笔记--函数对象
关于函数对象,百度百科对它是这样定义的: 重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象.又称仿函数. 听起来确实很难懂,通过搜索我找到一篇 ...
- <数据结构与算法分析>读书笔记--最大子序列和问题的求解
现在我们将要叙述四个算法来求解早先提出的最大子序列和问题. 第一个算法,它只是穷举式地尝试所有的可能.for循环中的循环变量反映了Java中数组从0开始而不是从1开始这样一个事实.还有,本算法并不计算 ...
- <数据结构与算法分析>读书笔记--运行时间计算
有几种方法估计一个程序的运行时间.前面的表是凭经验得到的(可以参考:<数据结构与算法分析>读书笔记--要分析的问题) 如果认为两个程序花费大致相同的时间,要确定哪个程序更快的最好方法很可能 ...
- <数据结构与算法分析>读书笔记--数学知识复习
数学知识复习是<数据结构与算法分析>的第一章引论的第二小节,之所以放在后面,是因为我对数学确实有些恐惧感.不过再怎么恐惧也是要面对的. 一.指数 基本公式: 二.对数 在计算机科学中除非有 ...
- <数据结构与算法分析>读书笔记--实现泛型构件pre-Java5
面向对象的一个重要目标是对代码重用的支持.支持这个目标的一个重要的机制就是泛型机制:如果除去对象的基本类型外,实现的方法是相同的,那么我们就可以用泛型实现来描述这种基本的功能. 1.使用Object表 ...
- <数据结构与算法分析>读书笔记--运行时间中的对数及其分析结果的准确性
分析算法最混乱的方面大概集中在对数上面.我们已经看到,某些分治算法将以O(N log N)时间运行.此外,对数最常出现的规律可概括为下列一般法则: 如果一个算法用常数时间(O(1))将问题的大小削减为 ...
- <数据结构与算法分析>读书笔记--要分析的问题
通常,要分析的最重要的资源就是运行时间.有几个因素影响着程序的运行时间.有些因素(如使用编译器和计算机)显然超出了任何理论模型的范畴,因此,虽然它们是重要的,但是我们在这里还是不能考虑它们.剩下的主要 ...
- <数据结构与算法分析>读书笔记--模型
为了在正式的构架中分析算法,我们需要一个计算模型.我们的模型基本上是一台标准的计算机,在机器中指令被顺序地执行.该模型有一个标准的简单指令系统,如加法.乘法.比较和赋值等.但不同于实际计算机情况的是, ...
- <数据结构与算法分析>读书笔记--递归
一.什么是递归 程序调用自身的编程技巧称为递归( recursion).递归做为一种算法在程序设计语言中广泛应用. 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的 ...
随机推荐
- 解决代码报红:Cannot resolve symbol 'xxx'
直接复制别人的代码,maven依赖到自己的IDEA中,个别代码报红,说是不能加载这个东西,检查代码没错,依赖没错,引入jar包也没错 最后网上找到了解决方法,参考文章 如上图所示,一般建议点击Inva ...
- 快速导出云服务器mysql的表数据
1.许多互联网应用的数据库都布署在远程的Linux云服务器上,我们经常要编辑表数据,导出表数据. 通常的做法是ssh连接到服务器,然后命令登录,命令查询导出数据,费时费力,效率低下. 安装TreeSo ...
- Retrofit 2.0 使用和原理
使用教程: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1016/3588.html retrofit2 与okhttp关系 ...
- HDU3625(SummerTrainingDay05-N 第一类斯特林数)
Examining the Rooms Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Othe ...
- 前端面试(原生js篇) - DOM
根据我的面试经历,一般小公司的面试环节,比较关心框架的熟练程度,以及独立开发组件的能力 但大厂通常有五轮以上的面试,而且对 js 基础语法很是看重 于是我总结了一些关于 js 基础的面试对话,有的当时 ...
- thinkphp5+qrcode生成二维码
1.下载二维码插件Phpqrcode,地址 https://sourceforge.net/projects/phpqrcode/files/,把下载的文件夹放到\thinkphp\vendor下 2 ...
- Stylus基本使用
介绍 在学习一个 Vue.js 项目的过程中,注意到源码中样式的部分并没有用熟悉的 .css 样式文件,而是发现了代码长得和 CSS 相像的 .styl 文件.这个 .styl 以前没见过啊,你是谁? ...
- 全局 DOM 变量
全局 DOM 变量 你可能已经知道,声明一个全局变量(使用 var 或者不使用)的结果并不仅仅是创建一个全局变量,而且还会在 global 对象(在浏览器中为 window )中创建一个同名属性. 还 ...
- Angular调用父Scope的函数
app.directive('toggle', function(){ return { restrict: 'A', template: '<a ng-click="f()" ...
- KeyPress 和KeyDown 、KeUp之间的区别
前几天,在写完一个功能模块上线测试的时候,出现了一个诡异的问题.input 框在输入查询内容之后,按回车键居然有两种不同的表现形式(input 框没有绑定键盘事件),谷歌和火狐功能正常,但IE在按了回 ...