Tips

《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。

在这里第一时间翻译成中文版。供大家学习分享之用。

30. 优先使用泛型方法

正如类可以是泛型的,方法也可以是泛型的。 对参数化类型进行操作的静态工具方法通常都是泛型的。 集合中的所有“算法”方法(如binarySearch和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. 优先使用泛型方法的更多相关文章

  1. Effective Java 第三版——33. 优先考虑类型安全的异构容器

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  2. Effective Java 第三版——44. 优先使用标准的函数式接口

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  3. Effective Java 第三版——46. 优先考虑流中无副作用的函数

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. Effective Java 第三版——47. 优先使用Collection而不是Stream来作为方法的返回类型

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  5. Effective Java 第三版——23. 优先使用类层次而不是标签类

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  6. Effective Java 第三版——24. 优先考虑静态成员类

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  7. Effective Java 第三版——29. 优先考虑泛型

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  8. Effective Java 第三版——81. 优先使用并发实用程序替代wait和notify

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  9. 《Effective Java 第三版》新条目介绍

    版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...

随机推荐

  1. 日志采集框架Flume以及Flume的安装部署(一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统)

    Flume支持众多的source和sink类型,详细手册可参考官方文档,更多source和sink组件 http://flume.apache.org/FlumeUserGuide.html Flum ...

  2. BIOS 品牌快捷键

    主板品牌 启动按键 笔记本品牌 启动按键 台式机品牌 启动按键 华硕主板 F8 联想笔记本 F12 联想台式机 F12 技嘉主板 F12 宏基笔记本 F12 惠普台式机 F12 微星主板 F11 华硕 ...

  3. IRP的同步

    应用层对设备的同步与异步操作 以WriteFile为例,一般的同步操作是调用WriteFile完成后,并不会返回,应用程序会在此处暂停,一直等到函数将数据写入文件中并正常返回,而异步操作则是调用Wri ...

  4. 使用JavaScript 操作本地文件

    一.显示用户选择文件[图片] <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  5. For循环将将数字集合分类写入字典

    有以下数字集合[11,22,33,44,55,66,77,88,99,25,35,45,66,88],将所有大于66的值保存至字典的第一个key中,将小于66的值保存至第二个key的值中.即{'k1' ...

  6. 【译】巧用CSS变量实现自动前缀

    转:https://www.h5jun.com/post/autoprefixing-with-css-variables-lea-verou.html 最近,当我在制作 markapp.io 这个小 ...

  7. c#中常用集合类和集合接口之接口系列【转】

    常用集合接口系列:http://www.cnblogs.com/fengxiaojiu/p/7997704.html 常用集合类系列:http://www.cnblogs.com/fengxiaoji ...

  8. Linux ssh双向免密认证

    一.实现原理 使用一种被称为"公私钥"认证的方式来进行ssh登录."公私钥"认证方式简单的解释是: 首先在客户端上创建一对公私钥(公钥文件:~/.ssh/id_ ...

  9. 不用第三方解码码取得图片宽高 附完整C++算法实现代码

    在特定的应用场景下,有时候我们只是想获取图片的宽高, 但不想通过解码图片才取得这个信息. 预先知道图片的宽高信息,进而提速图片加载,预处理等相关操作以提升体验. 在stackoverflow有一篇相关 ...

  10. 开发 | 微信小程序API-wx.setScreenBrightness/wx.getScreenBrightness

    前言 最近接触了微信小程序 API - wx.setScreenBrightness .wx.getScreenBrightness 接口,调用该接口可以调节并显示手机屏幕亮度数据.对于喜欢腾讯新闻. ...