<数据结构与算法分析>读书笔记--利用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).递归做为一种算法在程序设计语言中广泛应用. 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的 ...
随机推荐
- Java虚拟机--Java内存区域的划分和异常
Java内存区域的划分和异常 运行时数据区域 JVM在运行Java程序时候会将内存划分为若干个不同的数据区域. 程序计数器 线程私有.可看作是当前线程所执行的字节码的行号指示器,字节码解释器的工作是通 ...
- 【Java并发编程】7、线程池
1. 为什么使用线程池 诸如 Web 服务器.数据库服务器.文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务.请求以某种方式到达服务器,这种方式可能是通过网络协 ...
- jdk源码->集合->HashMap
一.hash算法 1.1 hash简介 hash,一般翻译为散列,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出值就是散列值,这种转换是一种压缩映射,也就是散列的空间小于输入的空间, ...
- K8s-Demo实现
Kubernates的基础界面 常用的操作 将创建好的yaml文件通过Create按钮创建所需资源项目. Dashbord: 可以通过Dashbord查看集群详情:cpu.memory.f ...
- python学习之老男孩python全栈第九期_day009之初始函数初窥
'''# len# 计算字符串的长度# s = '金老板小护士'# len(s)# 不能用 len 怎么办#low一点的方法# count = 0# for i in s:# count += 1# ...
- ubuntu16.4+nginx+uwsgi+Django 部署上线
Nginx概述 Nginx是一款轻量级的HTTP服务器,采用事件驱动和异步非阻塞处理方式框架,这让其具有极好的IO性能,市场用于服务端的反向代理和负载均衡 Nginx优点 高并发连接:官方测试Ngin ...
- 【代码笔记】iOS-获得现在的时间(2015-09-11)
一,代码. - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, ...
- 从零开始学习html(十一)CSS盒模型——上
一.元素分类 在CSS中,html中的标签元素大体被分为三种不同的类型:块状元素.内联元素(又叫行内元素)和内联块状元素. 常用的块状元素有: <div>.<p>.<h1 ...
- 对HTML的理解及常用标签使用介绍--来自我的百度前端技术学院的笔记
HTML是什么,HTML5是什么? ——HTML:超文本标记语言,一种用于创建网页的标准标记语言: ——HTML5:目前最新的HTML标准,包含新的元素.属性.行为,基于它们的功能特征将他们分成不同的 ...
- SD从零开始47-50, 装运成本基础、控制、结算, 信用/风险管理概述
[原创] SD从零开始47 装运成本基础 详细的装运成本处理Shipment Cost Processing in Detail 装运成本计算和装运成本结算可用于内向和外向交货: 装运成本记录在一张新 ...