欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

第8章泛型程序设计

时间:2023-07-05
第8章 泛型程序设计

泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后进行酱紫类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合类。从表面上来看,泛型很像C++中的模板。

8.1 为什么要使用泛型程序设计

泛型程序设计(Generic programming)意味着编写的代码可以被很多不同类型的对象所重用。

8.1.1 类型参数的好处

泛型提供了一个类型参数(type parameters),可以用来指示元素的类型

ArrayList files = new ArrayList();

这样使得代码具有更好的可读性

8.2 定义简单泛型类

一个泛型类(generic class)就是具有一个或多个类型变量的类,例如以下一个Pair类的代码

package chap8.pair1;public class Pair { private T first; private T second; public Pair() { first = null; second = null;} public Pair(T first, T second) {this.first = first; this.second = second;} public T getFirst() { return first; } public T getSecond() { return second; } public void setFirst(T newValue) { first = newValue; } public void setSecond(T newValue) { second = newValue; }}

Pair类引入了一个类型变量T,用尖括号<>括起来,并放在类名的后面。泛型类可以有多个类型变量

public class Pair {...}

类定义中的类型变量指定方法的返回类型以及与和局部变量的类型

private T first;

用具体的类型替换类型变量就可以实例化泛型类型

Pair

8.3 泛型方法

class ArrayAlg{public static T getMiddle(T..、a){return a[a.length/2];}}

这个方法是在普通类中定义的,而不是在泛型类中定义的。然而,这是一个泛型方法。注意,类型变量放在修饰符(这里是public static)的后面,返回类型的前面

泛型方法可以定义在普通类中,也可以定义在泛型类中

调用一个泛型方法是,在方法名前的前括号中放入具体的类型

String middle = ArrayAlg.getMiddle("Joho", "Q.", "Public");

大多数情况下,可以省略类型参数,编译器有足够的信息能够推断所调用的方法。但是有的时候,编译器也会提示错误

double middle = ArrayAlg.getMiddle(3.14, 1729, 0);

编译器会自动打包参数为1个Double和2个Integer对象,而后寻找这些类的共同超类型,找到Number和Comparable接口,其本身也是一个泛型类型。在这种情况下,可以采取的补救措施是将所有的参数写为double值

8.4 类型变量的限定

有的时候,类或方法需要对类型变量加以约束,例如,需要类型变量实现了CompareTo方法,解决方案是将类型变量限制为实现了Comparable接口的类

class ArrayAlg{ public static T min(T[] a) { if (a==null || a.length == 0) return null; T smallest = a[0]; for(int i = 1; i < a.length; i++) if(smallest.compareTo(a[i]) > 0) smallest = a[i]; return smallest; }}

这里为什么使用extends而不是implements,毕竟Comparable是一个接口,可以理解为:

这里的T应该是绑定类型的值类型(subtype)。T和绑定类型可以是类,也可以是接口。选择关键词extends的原因是更接近子类的概念,并且Java的设计者也不打算在语言中再添加一个新的关键字(如sub)

一个类型变量或者通配符可以有多个限定,例如

T extends Comparable & Serializable

限定类型用&分隔,而逗号用来分隔类型变量

利用泛型重新编写了一个minmax,这个方法计算泛型数组的最大值和最小值,并返回Pair

pair2/PairTest2.java

package chap8.pair2;import java.time.LocalDate;public class PairTest2 { public static void main(String[] args) { LocalDate[] birthdays = { LocalDate.of(1906, 12, 9), LocalDate.of(1815, 12, 10), LocalDate.of(1903, 12, 3), LocalDate.of(1910, 6, 22), }; Pair mm = ArrayAlg.minmax(birthdays); System.out.println("min = " + mm.getFirst()); System.out.println("max = " + mm.getSecond()); }}class ArrayAlg{ public static Pair minmax(T[] a) { if(a == null || a.length == 0) return null; T min = a[0]; T max = a[0]; for (int i = 1; i < a.length; i++) { if (min.compareTo(a[i]) > 0) min = a[i]; if (max.compareTo(a[i]) < 0) max = a[i]; } return new Pair<>(min, max); }}

8.5 泛型代码和虚拟机

虚拟机没有泛型类型对象——所有对象都属于普通类

8.5.1 类型擦除

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。原始类型的名字就是删除类型参数后的泛型类型名。擦除类型变量,并替换为限定类型。

8.5.2 翻译泛型表达式

如果程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。

Pair buddies = ...;Employee buddy = buddies.getFirst();

8.5.3 翻译泛型方法

class DateInterval extends Pair{public void setSecond(LocalDate second) { if(second.compareTo(getFirst()) >= 0) super.setSecond(second); }}

如上代码,一个日期区间是一对LocalDate对象,并且需要覆盖这个方法来确保第二个值永远不小于第一个值,擦除之后可能变为

class DateInterval extends Pair{ public void setSecond(LocalDate second) { ..、} ...}

令人奇怪的是,存在另一个从Pair继承的setSecond方法,即

public vodi setSecond(Object second)

这里希望对setSecond的调用具有多态性,编译器会生成一个桥方法(bridge method

public void setSecond(Object second){ setSecond((Date) second); }

8.5.4 调用遗留代码

设计Java泛型类型是,主要目标是允许泛型代码和遗留代码之间能够相互操作。

Dictionary labelTable = new Hashtable<>();labelTable.put(0, new JLabel(new ImageIcon("nine.gif")));labelTable.put(20, new JLabel(new ImageIcon("ten.gif")));

将Dictionary对象传递给setLabelTable时,编译器会发出一个警告

slider.setLabelTable(labelTable); //Warning

在查看了警告之后,可以利用注解(annotation)使之消失,注释必须放在生成这个警告的代码所在的方法之前,如下:

@SuppressWarnings("unchecked")Dictionary labelTable = slider.getLabelTable(); // No warning

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。