Effective Java 第三版——30. 优先使用泛型方法
Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。
30. 优先使用泛型方法
正如类可以是泛型的,方法也可以是泛型的。 对参数化类型进行操作的静态工具方法通常都是泛型的。 集合中的所有“算法”方法(如binarySearc
h和sort
)都是泛型的。
编写泛型方法类似于编写泛型类型。 考虑这个方法,它返回两个集合的并集:
// Uses raw types - unacceptable! [Item 26]
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
此方法可以编译但有两个警告:
Union.java:5: warning: [unchecked] unchecked call to
HashSet(Collection<? extends E>) as a member of raw type HashSet
Set result = new HashSet(s1);
^
Union.java:6: warning: [unchecked] unchecked call to
addAll(Collection<? extends E>) as a member of raw type Set
result.addAll(s2);
^
要修复这些警告并使方法类型安全,请修改其声明以声明表示三个集合(两个参数和返回值)的元素类型的类型参数,并在整个方法中使用此类型参数。 声明类型参数的类型参数列表位于方法的修饰符和返回类型之间。 在这个例子中,类型参数列表是<E>
,返回类型是Set<E>
。 类型参数的命名约定对于泛型方法和泛型类型是相同的(条目 29和68):
// Generic method
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
至少对于简单的泛型方法来说,就是这样。 此方法编译时不会生成任何警告,并提供类型安全性和易用性。 这是一个简单的程序来运行该方法。 这个程序不包含强制转换和编译时没有错误或警告:
// Simple program to exercise generic method
public static void main(String[] args) {
Set<String> guys = Set.of("Tom", "Dick", "Harry");
Set<String> stooges = Set.of("Larry", "Moe", "Curly");
Set<String> aflCio = union(guys, stooges);
System.out.println(aflCio);
}
当运行这个程序时,它会打印[Moe, Tom, Harry, Larry, Curly, Dick]
(输出中元素的顺序依赖于具体实现。)
union
方法的一个限制是所有三个集合(输入参数和返回值)的类型必须完全相同。 通过使用限定通配符类型( bounded wildcard types)(条目 31),可以使该方法更加灵活。
有时,需要创建一个不可改变但适用于许多不同类型的对象。 因为泛型是通过擦除来实现的(条目 28),所以可以使用单个对象进行所有必需的类型参数化,但是需要编写一个静态工厂方法来重复地为每个请求的类型参数化分配对象。 这种称为泛型单例工厂(generic singleton factory)的模式用于方法对象( function objects)(条目 42),比如Collections.reverseOrder
方法,偶尔也用于Collections.emptySet
之类的集合。
假设你想写一个恒等方法分配器( identity function dispenser)。 类库提供了Function.identity
方法,所以没有理由编写你自己的实现(条目 59),但它是有启发性的。 如果每次要求的时候都去创建一个新的恒等方法对象是浪费的,因为它是无状态的。 如果Java的泛型被具体化,那么每个类型都需要一个恒等方法,但是由于它们被擦除以后,所以泛型的单例就足够了。 以下是它的实例:
// Generic singleton factory pattern
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
}
将IDENTITY_FN
转换为(UnaryFunction <T>)
会生成一个未经检查的强制转换警告,因为UnaryOperator <Object>
对于每个T
都不是一个UnaryOperator <T>
。但是恒等方法是特殊的:它返回未修改的参数,所以我们知道,使用它作为一个UnaryFunction <T>
是类型安全的,无论T
的值是多少。因此,我们可以放心地抑制由这个强制生成的未经检查的强制转换警告。 一旦我们完成了这些,代码编译没有错误或警告。
下面是一个示例程序,它使用我们的泛型单例作为UnaryOperator <String>
和UnaryOperator <Number>
。 像往常一样,它不包含强制转化,编译时也没有错误和警告:
// Sample program to exercise generic singleton
public static void main(String[] args) {
String[] strings = { "jute", "hemp", "nylon" };
UnaryOperator<String> sameString = identityFunction();
for (String s : strings)
System.out.println(sameString.apply(s));
Number[] numbers = { 1, 2.0, 3L };
UnaryOperator<Number> sameNumber = identityFunction();
for (Number n : numbers)
System.out.println(sameNumber.apply(n));
}
虽然相对较少,类型参数受涉及该类型参数本身的某种表达式限制是允许的。 这就是所谓的递归类型限制(recursive type bound)。 递归类型限制的常见用法与Comparable
接口有关,它定义了一个类型的自然顺序(条目 14)。 这个接口如下所示:
public interface Comparable<T> {
int compareTo(T o);
}
类型参数T
定义了实现Comparable <T>
的类型的元素可以比较的类型。 在实际中,几乎所有类型都只能与自己类型的元素进行比较。 所以,例如,String
类实现了Comparable <String>
,Integer
类实现了Comparable <Integer>
等等。
许多方法采用实现Comparable
的元素的集合来对其进行排序,在其中进行搜索,计算其最小值或最大值等。 要做到这一点,要求集合中的每一个元素都可以与其中的每一个元素相比,换言之,这个元素是可以相互比较的。 以下是如何表达这一约束:
// Using a recursive type bound to express mutual comparability
public static <E extends Comparable<E>> E max(Collection<E> c);
限定的类型<E extends Comparable <E >>
可以理解为“任何可以与自己比较的类型E
”,这或多或少精确地对应于相互可比性的概念。
这里有一个与前面的声明相匹配的方法。它根据其元素的自然顺序来计算集合中的最大值,并编译没有错误或警告:
// Returns max value in a collection - uses recursive type bound
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("Empty collection");
E result = null;
for (E e : c)
if (result == null || [e.compareTo(result](http://e.compareTo(result)) > 0)
result = Objects.requireNonNull(e);
return result;
}
请注意,如果列表为空,则此方法将引发IllegalArgumentException
异常。 更好的选择是返回一个 Optional<E>
(条目 55)。
递归类型限制可能变得复杂得多,但幸运的是他们很少这样做。 如果你理解了这个习惯用法,它的通配符变体(条目 31)和模拟的自我类型用法(条目 2),你将能够处理在实践中遇到的大多数递归类型限制。
总之,像泛型类型一样,泛型方法比需要客户端对输入参数和返回值进行显式强制转换的方法更安全,更易于使用。 像类型一样,你应该确保你的方法可以不用强制转换,这通常意味着它们是泛型的。 应该泛型化现有的方法,其使用需要强制转换。 这使得新用户的使用更容易,而不会破坏现有的客户端(条目 26)。
Effective Java 第三版——30. 优先使用泛型方法的更多相关文章
- Effective Java 第三版——33. 优先考虑类型安全的异构容器
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——44. 优先使用标准的函数式接口
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——46. 优先考虑流中无副作用的函数
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——47. 优先使用Collection而不是Stream来作为方法的返回类型
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——23. 优先使用类层次而不是标签类
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——24. 优先考虑静态成员类
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——29. 优先考虑泛型
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——81. 优先使用并发实用程序替代wait和notify
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
随机推荐
- [python]使用xlrd对Excel表格进行读写操作
2.1 导入模块 import xlrd 2.2 打开Excel文件读取数据 data = xlrd.open_workbook("excelFile.xls") 2.3 使用技巧 ...
- blog建表操作
表思维导图: 数据库:表 from django.db import modelsfrom django.conf import settingsfrom django.contrib.auth. ...
- OLEDB数据源
title: OLEDB数据源 date: 2018-01-12 21:42:37 tags: [OLEDB, 数据库编程, VC++, 数据库] categories: windows 数据库编程 ...
- 【转载】ipcs与Linux共享内存
一.共享内存相关知识 所谓共享内存,就是多个进程间共同地使用同一段物理内存空间,它是通过将同一段物理内存映射到不同进程的 虚拟空间来实现的.由于映射到不同进程的虚拟空间中,不同进程可以直接使用,不需要 ...
- 【转载】Linux下的IO监控与分析
近期要在公司内部做个Linux IO方面的培训, 整理下手头的资料给大家分享下 各种IO监视工具在Linux IO 体系结构中的位置 源自 Linux Performance and Tuning G ...
- 记录:使用rpm安装JDK
从这个地址下载rpm文件http://www.oracle.com/technetwork/cn/java/javase/downloads/jdk8-downloads-2133151-zhs.ht ...
- Django之路由分发系统
web的基本工作流程 首先,我们先来思考一下我们平常在上网浏览网页时候的场景,大致就是打开一个web浏览器,输入某一个网站的地址,然后转到该网址,在浏览器中得到该网址的页面.从这个场景中我们可以抽象出 ...
- .net 平台下, Socket通讯协议中间件设计思路(附源码)
.net 平台下,实现通讯处理有很多方法(见下表),各有利弊: 序号 实现方式 特点 1 WCF 优点:封装好,方便.缺点:难学,不跨平台 2 RocketMQ,SuperSocket等中间件 优点: ...
- Yii2基本概念之——属性(property)
学习任何一门学问,往往都是从起基本的概念学起.万丈高楼平地起,这些基本概念就是高楼的基石,必须做详尽的分析.我们知道,Yii2是一款脉络清晰的框架,理顺了基础的概念和基本功能,学习更高级和复杂的功能就 ...
- 循序渐进之Spring AOP(2) - 基本概念
学习AOP前要先了解几个重要术语:Joinpoint.Pointcut.Advice 仍然以改装车比喻,拿到心爱的汽车后想做改装,第一件事是什么?找到要改装的地方.车上可改装的地方很多,但每个人感兴趣 ...