第30项:优先考虑泛型方法

  就如类可以从泛型中受益一般,方法也一样。静态工具方法尤其合适于泛型化。Collections中的所有“算法”方法(例如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);
                      ^

  为了修正这些警告,使方法变成类型安全的,要将方法声明修改为声明一个类型参数,表示这三个集合的元素类型(两个参数和一个返回值),并在方法中使用类型参数。声明类型参数的类型参数列表,处在方法的修饰符及其返回类型之间。在这个示例中,类型参数列表为,返回类型为Set。类型参数的命名惯例与泛型方法以及泛型的相同(见第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),用于函数对象(第42项),例如Collections.reverseOrder,偶尔用于集合,例如Collections.emptySet

  假设你要编写一个恒等函数(identity function)分配器。有些库库已经提供了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 )会生成未受检的强制转换警告,因为UnaryOperator对于每个T都不是UnaryOperator 。但恒等函数是特殊的:它返回的参数并未被修改过,因此我们知道无论T的值是什么,将它用作UnaryFunction 都是类型安全的。因此,我们可以自信地抑制由此强制转换生成的未受检的强制转换警告。完成此操作后,代码在编译时不会出现错误或警告:

// 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接口有关,它定义类型的自然顺序:

public interface Comparable<T> {
    int compareTo(T o); 
}

  类型参数T定义的类型,可以与实现Comparable的类型的元素进行比较。实际上,几乎所有的类型都只能与它们自身的类型的元素相比较。因此,例如String实现Comparable,Integer实现Comparable,等等。

  有许多方法都带有一个实现Comparable接口的元素列表,为了对列表进行排序,并在其中进行搜索,计算出它的最小值或者最大值,等等。要完成这其中的任何一项工作,要求列表中的每个元素都要能够与列表中的每个其他元素相比较,换句话说,列表的元素可以相互比较( mutually comparable)。下面是如何表达这种约束条件的一个示例:

// Using a recursive type bound to express mutual comparability
public static <E extends Comparable<E>> E max(Collection<E> c);

  类型限制<E extends Comparable>,可以读作“针对可以与自身进行比较的没个类型T”,这与互比性的概念或多或少有些一致。

  下面的方法就带有上述声明。它根据元素的自然顺序计算列表的最大值,编译时没有出现错误或者警告:

// 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) > 0)
            result = Objects.requireNonNull(e);
    return result;
}

  请注意,如果列表为空,则此方法抛出IllegalArgumentException。 更好的选择是返回Optional (第55项)。

  递归类型限制可能比这个要复杂得多,但幸运的是,这种情况并不经常发生。如果你理解了这种习惯用法及其通配符变量(第2项),就能够处理在实践中遇到的许多递归类型限制了。

  总而言之,泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法来得更加安全,也更加容易。就像类型一样,你应该确保你的方法可以不用转换就能使用,这通常意味着要将它们泛型化。并且就像类型一样,还应该将现有的方法泛型化,使新用户使用起来更加轻松,且不会破坏现有的客户端(第26项)。

第31项:利用有限制通配符来提升API的灵活性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值