Effective Java 第三版——30. 优先使用泛型方法

Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必不少人都读过,号称Java四大名著之一,不过第二版2009年出版,到如今已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深入的变化。
在这里第一时间翻译成中文版。供你们学习分享之用。java

Effective Java, Third Edition

 30. 优先使用泛型方法

正如类能够是泛型的,方法也能够是泛型的。 对参数化类型进行操做的静态工具方法一般都是泛型的。 集合中的全部“算法”方法(如binarySearch和sort)都是泛型的。算法

编写泛型方法相似于编写泛型类型。 考虑这个方法,它返回两个集合的并集:express

// 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):app

// 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;

}

至少对于简单的泛型方法来讲,就是这样。 此方法编译时不会生成任何警告,并提供类型安全性和易用性。 这是一个简单的程序来运行该方法。 这个程序不包含强制转换和编译时没有错误或警告:ide

// 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之类的集合。ui

假设你想写一个恒等方法分配器( identity function dispenser)。 类库提供了Function.identity方法,因此没有理由编写你本身的实现(条目 59),但它是有启发性的。 若是每次要求的时候都去建立一个新的恒等方法对象是浪费的,由于它是无状态的。 若是Java的泛型被具体化,那么每一个类型都须要一个恒等方法,可是因为它们被擦除之后,因此泛型的单例就足够了。 如下是它的实例:url

// 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)。

相关文章
相关标签/搜索