【codejava】第八版:第十二章 泛型程序设计[001] [20180106]
2018-01-06 14:25
435 查看
12.1 为什么要使用泛型程序设计
generic programming意味着编写的代码可以被很多不同类型的对象所重用,例如,我们并不希望为聚集String和File而编写不同的类,实际上也不需要这样做,因为一个ArrayList可以聚集任何类型的对象,这是一个泛型程序设计的实例
java SE 5.0之前 java泛型程序设计是用继承实现的 ArrayList只维护一个Obj引用的数组 这样的实现有两个问题
1当获取一个值时必须进行强制类型转换,此外这里没有检查错误,可以向数据列表中添加任何类的对象
ArrayList list = new ArrayList();
list.add("lee");
System.out.println((String)list.get(0));
list.add(new File("..."));
对于这个调用 编译和运行都不会出错,然而在其他地方,如果将get的结果强制类型转换为String类型 就会产生一个错误
泛型提供了一个更好的解决方案 :类型参数(type parameters),ArrayList类有一个类型参数用来指示元素的类型
ArrayList<String> list = new ArrayList<String>();
这使得代码具有更好的可读性,人们一看就知道这个数组列表中包含的是String对象 编译器也可以很好的利用这个信息,当调用get的时候,不需要进行强制类型转换,编译器就知道返回值类型为String而不是Obj。
编译器还知道list中add方法有一个类型为String的参数 这将比使用Obj类型的参数安全一些,现在,编译器可以进行检查,避免插入错误类型的对象
list.add(new File("..."));
这时是无法通过编译的,出现编译错误比类在运行时出现类的强制类型转转异常要好得多,类型参数的魅力在于,使得程序具有更好的可读性和安全性.
泛型程序设计划分为3个熟练级别,基本级别是,仅仅使用泛型类--典型的是像ArrayList这样的集合-不必考虑他们的工作方式与原因,大多数应用程序员将会停留在这一级别上,知道出现了什么问题(书中到此没有详细讲明哪3个级别)
12.2 简单泛型类的定义
一个泛型类(generic class)就是具有一个或多个类型变量的类
public class Pair<T> {
public Pair() {
super();
}
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
private T first;
private T second;
}
Pair类引入了一个类型变量T,用尖括号(<>)括起来,并放在类名的后面,泛型类可以有多个类型变量,例如,可以定义pair类,其中第一个域和第二个域使用不同的类型;
public class Pair<T, U>
类型变量使用大写形式,且比较短,这是很常见的,在java库中,使用变量E表示集合元素的类型,K和V分别表示表的关键字的键值对(Key-Value)的类型,T(需要时还可以用临近的字母U和S)表示“任意类型”
public class PairTest1 {
public static void main(String[] args) {
String[] words = {"Mary","had"," a"," little"," lamb"};
Pair<String> pair = PairAlg.minmax(words);
System.out.println("min:"+pair.getFirst());
System.out.println("max:"+pair.getSecond());
}
}
class PairAlg{
public static Pair<String> minmax(String[] arr){
if (arr == null || arr.length == 0) {
return null;
}
String max = arr[0];
String min = arr[0];
for(int i=0; i<arr.length; i++){
if(max.compareTo(arr[i]) < 0){
max = arr[i];
}if(min.compareTo(arr[i]) > 0){
min = arr[i];
}
}
return new Pair<String>(min, max);
}
}
12.3 泛型的方法
class PairAlg{
public static <T> T getMiddle(T[] arr){
return arr[arr.length / 2];
}
}
这个方法是在普通类中定义的,然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意,类型变量放在修饰符(public static)的后面,返回类型的前面.泛型方法可以定义在普通类中,也可以定义在泛型类中. 当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
String[] words = {"Mary","had"," a"," little"," lamb"};
Integer[] nums = {2, 5, 6,8,9};
System.out.println(PairAlg.<Integer>getMiddle(nums));//注意尖括号位置
System.out.println(PairAlg.<String>getMiddle(words));//注意尖括号位置
在大多数情况下,可以省略尖括号,因为编译器有足够的信息能推算出所调用的方法,它用names的类型与泛型类型T进行匹配并推断出T一定是该类型
12.4 类型变量的限定
有时,类或方法需要对类型变量加以约束,下面这个我们计算数据中的最小元素
public static <T extends Comparable> T min(T[] arr){
if (arr == null || arr.length == 0) {
return null;
}
T smallest = arr[0];
for(int i=0; i<arr.length; i++){
if(smallest.compareTo(arr[i]) > 0){
smallest = arr[i];
}
}
return smallest;
}
实际上Comparable接口本身就是一个泛型类型,目前,我们忽略其复杂性以及编译器产生的警告,在12.8节有介绍怎么适当地使用类型参数,或许大家会感到奇怪,在此为什么使用关键字extends,Comparable不是一个接口吗?
表示T应该是绑定类型的子类型(subtype),T和绑定类型可以是类,也可以是接口,选择关键字extends的原因是更接近子类的概念,并且JAVA的设计者也不打算在语言中再添加一个新的关键字
一个类型变量或通配符可以有多个限定,例如
public class PairTest2 {
public static void main(String[] args) {
GregorianCalendar[] birthdays = {
new GregorianCalendar(1988, Calendar.JUNE, 15),
new GregorianCalendar(1970, Calendar.APRIL, 6),
new GregorianCalendar(1964, Calendar.FEBRUARY, 31),
new GregorianCalendar(1990, Calendar.NOVEMBER, 23)
};
Pair<GregorianCalendar> gregorianCalendar = minmax(birthdays);
System.out.println("min:"+gregorianCalendar.getFirst());
System.out.println("max:"+gregorianCalendar.getSecond());
}
public static <T extends Comparable & Serializable> Pair<T> minmax(T[] arr){
if (arr == null || arr.length == 0) {
return null;
}
T max = arr[0];
T min = arr[0];
for(int i=0; i<arr.length; i++){
if(max.compareTo(arr[i]) < 0){
max = arr[i];
}if(min.compareTo(arr[i]) > 0){
min = arr[i];
}
}
return new Pair<T>(min, max);
}
}
12.5 泛型代码和虚拟机
虚拟机没有泛型类型对象-所有对象都属于普通类,在泛型实现的早期版本中,甚至能够将使用泛型的程序编译为1.0虚拟机上运行的类文件
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名.擦除(erased)类型变量,并替换为限定类型(无限定的变量用Obj)
public class Pair<T> {
public Pair() {
super();
}
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
private T first;
private T second;
}
上面的Pair<T>原始类型如下
public class Pair {
public Pair() {
super();
}
public Pair(Object first, Object second) {
super();
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
public void setFirst(Object first) {
this.first = first;
}
public void setSecond(Object second) {
this.second = second;
}
private Object first;
private Object second;
}
因为T是一个无限定的类型,所以直接用Obj来进行替换
的出来的结果是一个普通的类,就像泛型引入java语言之前已经实现的那样
在程序中可以包含不同类型的Pair,例如Pair<String>,Pair<GregorianCalendar>,而擦除类型后就变成原始的Pair类型了
原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Obj替换
12.5.1 翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换,例如,下面这个语句序列
Pair<GregorianCalendar> gregorianCalendar = minmax(birthdays);
GregorianCalendar min = gregorianCalendar.getFirst();
擦除getFirst()返回类型后将返回Obj类型,编译器自动插入GregorianCalendar的强制类型转换,也就是说,编译器吧这个方法调用翻译为两条虚拟机指令:
.对原始方法Pair.getFirst()的调用
.将返回的Obj类型强制转换为gregorianCalendar
当存取一个泛型域时也要插入强制类型转换,假设Pair类的fiirst域和second域都是公有的(也许这不是一种良好的编程习惯,但在java中是合法的).表达式:
GregorianCalendar min = gregorianCalendar.first();
也会在结果字节码中插入强制类型转换.
12.5.2 翻译泛型方法 (12.5.2 & 12.5.3有点理解不了 所以都没有详细做笔记 以后再深入研究)
类型擦除也会出现在泛型方法中,程序员通常认为下述的泛型方法
是一个完整的方法族,而擦除类型之后,只剩下一个方法
12.5.3 调用遗留代码
12.6 约束和局限性
在下面几节中,将阐述使用java泛型时需要考虑的一些限制,大多数限制都是由类型擦除引起的.
12.6.1 不能用基本类型实例化参数 没有Pair<double>只有Pair<Double>
12.6.2 运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特性的非泛型类型,因此,所有的类型查询只产生原始类型(暂时跳过这一章,用的少后面不好理解,以后攻破)
generic programming意味着编写的代码可以被很多不同类型的对象所重用,例如,我们并不希望为聚集String和File而编写不同的类,实际上也不需要这样做,因为一个ArrayList可以聚集任何类型的对象,这是一个泛型程序设计的实例
java SE 5.0之前 java泛型程序设计是用继承实现的 ArrayList只维护一个Obj引用的数组 这样的实现有两个问题
1当获取一个值时必须进行强制类型转换,此外这里没有检查错误,可以向数据列表中添加任何类的对象
ArrayList list = new ArrayList();
list.add("lee");
System.out.println((String)list.get(0));
list.add(new File("..."));
对于这个调用 编译和运行都不会出错,然而在其他地方,如果将get的结果强制类型转换为String类型 就会产生一个错误
泛型提供了一个更好的解决方案 :类型参数(type parameters),ArrayList类有一个类型参数用来指示元素的类型
ArrayList<String> list = new ArrayList<String>();
这使得代码具有更好的可读性,人们一看就知道这个数组列表中包含的是String对象 编译器也可以很好的利用这个信息,当调用get的时候,不需要进行强制类型转换,编译器就知道返回值类型为String而不是Obj。
编译器还知道list中add方法有一个类型为String的参数 这将比使用Obj类型的参数安全一些,现在,编译器可以进行检查,避免插入错误类型的对象
list.add(new File("..."));
这时是无法通过编译的,出现编译错误比类在运行时出现类的强制类型转转异常要好得多,类型参数的魅力在于,使得程序具有更好的可读性和安全性.
泛型程序设计划分为3个熟练级别,基本级别是,仅仅使用泛型类--典型的是像ArrayList这样的集合-不必考虑他们的工作方式与原因,大多数应用程序员将会停留在这一级别上,知道出现了什么问题(书中到此没有详细讲明哪3个级别)
12.2 简单泛型类的定义
一个泛型类(generic class)就是具有一个或多个类型变量的类
public class Pair<T> {
public Pair() {
super();
}
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
private T first;
private T second;
}
Pair类引入了一个类型变量T,用尖括号(<>)括起来,并放在类名的后面,泛型类可以有多个类型变量,例如,可以定义pair类,其中第一个域和第二个域使用不同的类型;
public class Pair<T, U>
类型变量使用大写形式,且比较短,这是很常见的,在java库中,使用变量E表示集合元素的类型,K和V分别表示表的关键字的键值对(Key-Value)的类型,T(需要时还可以用临近的字母U和S)表示“任意类型”
public class PairTest1 {
public static void main(String[] args) {
String[] words = {"Mary","had"," a"," little"," lamb"};
Pair<String> pair = PairAlg.minmax(words);
System.out.println("min:"+pair.getFirst());
System.out.println("max:"+pair.getSecond());
}
}
class PairAlg{
public static Pair<String> minmax(String[] arr){
if (arr == null || arr.length == 0) {
return null;
}
String max = arr[0];
String min = arr[0];
for(int i=0; i<arr.length; i++){
if(max.compareTo(arr[i]) < 0){
max = arr[i];
}if(min.compareTo(arr[i]) > 0){
min = arr[i];
}
}
return new Pair<String>(min, max);
}
}
12.3 泛型的方法
class PairAlg{
public static <T> T getMiddle(T[] arr){
return arr[arr.length / 2];
}
}
这个方法是在普通类中定义的,然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意,类型变量放在修饰符(public static)的后面,返回类型的前面.泛型方法可以定义在普通类中,也可以定义在泛型类中. 当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
String[] words = {"Mary","had"," a"," little"," lamb"};
Integer[] nums = {2, 5, 6,8,9};
System.out.println(PairAlg.<Integer>getMiddle(nums));//注意尖括号位置
System.out.println(PairAlg.<String>getMiddle(words));//注意尖括号位置
在大多数情况下,可以省略尖括号,因为编译器有足够的信息能推算出所调用的方法,它用names的类型与泛型类型T进行匹配并推断出T一定是该类型
12.4 类型变量的限定
有时,类或方法需要对类型变量加以约束,下面这个我们计算数据中的最小元素
public static <T extends Comparable> T min(T[] arr){
if (arr == null || arr.length == 0) {
return null;
}
T smallest = arr[0];
for(int i=0; i<arr.length; i++){
if(smallest.compareTo(arr[i]) > 0){
smallest = arr[i];
}
}
return smallest;
}
public static <T extends Comparable>
实际上Comparable接口本身就是一个泛型类型,目前,我们忽略其复杂性以及编译器产生的警告,在12.8节有介绍怎么适当地使用类型参数,或许大家会感到奇怪,在此为什么使用关键字extends,Comparable不是一个接口吗?
<T extends Bounding Type>
表示T应该是绑定类型的子类型(subtype),T和绑定类型可以是类,也可以是接口,选择关键字extends的原因是更接近子类的概念,并且JAVA的设计者也不打算在语言中再添加一个新的关键字
一个类型变量或通配符可以有多个限定,例如
<T extends Comparable & Serializable>
public class PairTest2 {
public static void main(String[] args) {
GregorianCalendar[] birthdays = {
new GregorianCalendar(1988, Calendar.JUNE, 15),
new GregorianCalendar(1970, Calendar.APRIL, 6),
new GregorianCalendar(1964, Calendar.FEBRUARY, 31),
new GregorianCalendar(1990, Calendar.NOVEMBER, 23)
};
Pair<GregorianCalendar> gregorianCalendar = minmax(birthdays);
System.out.println("min:"+gregorianCalendar.getFirst());
System.out.println("max:"+gregorianCalendar.getSecond());
}
public static <T extends Comparable & Serializable> Pair<T> minmax(T[] arr){
if (arr == null || arr.length == 0) {
return null;
}
T max = arr[0];
T min = arr[0];
for(int i=0; i<arr.length; i++){
if(max.compareTo(arr[i]) < 0){
max = arr[i];
}if(min.compareTo(arr[i]) > 0){
min = arr[i];
}
}
return new Pair<T>(min, max);
}
}
12.5 泛型代码和虚拟机
虚拟机没有泛型类型对象-所有对象都属于普通类,在泛型实现的早期版本中,甚至能够将使用泛型的程序编译为1.0虚拟机上运行的类文件
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名.擦除(erased)类型变量,并替换为限定类型(无限定的变量用Obj)
public class Pair<T> {
public Pair() {
super();
}
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
private T first;
private T second;
}
上面的Pair<T>原始类型如下
public class Pair {
public Pair() {
super();
}
public Pair(Object first, Object second) {
super();
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
public void setFirst(Object first) {
this.first = first;
}
public void setSecond(Object second) {
this.second = second;
}
private Object first;
private Object second;
}
因为T是一个无限定的类型,所以直接用Obj来进行替换
的出来的结果是一个普通的类,就像泛型引入java语言之前已经实现的那样
在程序中可以包含不同类型的Pair,例如Pair<String>,Pair<GregorianCalendar>,而擦除类型后就变成原始的Pair类型了
原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Obj替换
12.5.1 翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换,例如,下面这个语句序列
Pair<GregorianCalendar> gregorianCalendar = minmax(birthdays);
GregorianCalendar min = gregorianCalendar.getFirst();
擦除getFirst()返回类型后将返回Obj类型,编译器自动插入GregorianCalendar的强制类型转换,也就是说,编译器吧这个方法调用翻译为两条虚拟机指令:
.对原始方法Pair.getFirst()的调用
.将返回的Obj类型强制转换为gregorianCalendar
当存取一个泛型域时也要插入强制类型转换,假设Pair类的fiirst域和second域都是公有的(也许这不是一种良好的编程习惯,但在java中是合法的).表达式:
GregorianCalendar min = gregorianCalendar.first();
也会在结果字节码中插入强制类型转换.
12.5.2 翻译泛型方法 (12.5.2 & 12.5.3有点理解不了 所以都没有详细做笔记 以后再深入研究)
类型擦除也会出现在泛型方法中,程序员通常认为下述的泛型方法
是一个完整的方法族,而擦除类型之后,只剩下一个方法
12.5.3 调用遗留代码
12.6 约束和局限性
在下面几节中,将阐述使用java泛型时需要考虑的一些限制,大多数限制都是由类型擦除引起的.
12.6.1 不能用基本类型实例化参数 没有Pair<double>只有Pair<Double>
12.6.2 运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特性的非泛型类型,因此,所有的类型查询只产生原始类型(暂时跳过这一章,用的少后面不好理解,以后攻破)
相关文章推荐
- 【codejava】第八版:第五章 继承[001] [20180108]
- 【codejava】第八版:第十四章 多线程[001] [20180105]
- Java 语言程序设计基础篇原书第八版_第十二章_第八题_程序分享
- 【codejava】第八版:第十三章 集合[001] [20180104]
- Java语言程序设计-基础篇-第八版-复习题-第五章
- JAVA基础【8.1】《Java核心技术1》泛型程序设计-泛型
- java高级程序设计-拆泛型
- Java语言程序设计-基础篇-第八版-复习题-第六章
- Java基础知识六:泛型程序设计
- java核心技术卷 之泛型程序设计
- Java实践(三)---泛型程序设计
- Java程序设计9——泛型
- Java语言程序设计-基础篇-第八版-编程练习题-第五章
- Java语言程序设计-基础篇-第八版-复习题-第八章
- java语言程序设计 第十二章 (12.28、12.30、12.33)
- Java语言程序设计-基础篇-第八版-第三章
- Java语言程序设计-基础篇-第八版-复习题-第九章
- Java语言程序设计-基础篇-第八版-第二章
- java_泛型 TreeSet 判断hashcode/length(升序排列)
- Java语言程序设计-基础篇-第八版-复习题-第三章