天天看点

【笔记30】优先考虑泛型方法

       就如类可以从泛型中受益一般,方法也一样。静态工具方法尤其适合于泛型化。Collections中的所有“算法”方法(例如binarySearch和sort)都泛型化了。

        编写泛型方法与编写泛型类型相类似。例如下面这个方法,它返回两个集合的联合:

public static Set union(Set s1, Set s2) {  
    Set result = new HashSet(s1);  
    result.addAll(s2);  
    return result;  
}  
           

可以编译,但是有三条警告:

Multiple markers at this line
    - Type safety: The constructor HashSet(Collection) belongs to the raw type HashSet. References to 
     generic type HashSet<E> should be parameterized
    - Set is a raw type. References to generic type Set<E> should be parameterized
    - HashSet is a raw type. References to generic type HashSet<E> should be parameterized
           

-类型安全:构造函数HashSet(集合)属于原始类型的HashSet。引用泛型的HashSet应该是参数化的

-Set是一种原始类型。对泛型类型设置的引用应该是参数化的

-HashSet是一种原始类型。对泛型类型的HashSet的引用应该是参数化的 

       为了修正这些警告要将方法声明修改为声明一个类型参数,表示这三个集合的元素类型(两个参数及一个返回值),并在方法中使用类型参数。声明类型参数的类型参数列表,处在方法的修饰符及其返回类型之间,修改后的代码如下:

public static <E> Set<E> union(Set<E> s1, Set<E> s2) {  
    Set<E> result = new HashSet<E>(s1);  
    result.addAll(s2);  
    return result;  
}  
           

       上面的union方法即为一般的泛型方法,但是它有一个限制,要求三个集合的类型(两个输入参数及一个返回值)必须全部相同。利用有限制的通配符类型可以使这个方法变得更加灵活。第28条会详细介绍

       泛型方法的一个显著特征是,无需明确指定类型参数的值,不像调用泛型构造器的时候是必须要指定类型参数的,因为泛型方法的类型会存在一个类型推导的过程(编译器通过检查方法参数的类型来计算类型的值),对于上述程序而言,编译器发现union的两个参数都是set<String>类型,因此知道类型参数E必须是String。在调用泛型构造器的时候,要明确传递类型参数的值可能有点麻烦。类型参数出现在了变量的声明的左右两边,显得冗余:

Map<String, List<String>> anagrams = new HashMap<String, List<String>>();  
           

对于这情况 ,可以遵照第一条,提供一个静态工厂方法来简化:

public static <K, V> HashMap<K, V> newHashMap() {  
    return new HashMap<K, V>();  
}  
           
Map<String, List<String>> map = newHashMap() ;
           

利用上面的静态工厂方法,我们可以把变量声明右侧的参数类型省略掉,当参数类型多而复杂时尤其有效。

       相关的模式是泛型单例工厂(generic singleton factory)。有时,会需要创建不可变但又适合于许多不同类型的对象。由于泛型是通过擦除实现的,可以给所有必须的类型参数使用单个对象,但是需要编写一个静态工厂方法,重复的给每个必要的类型参数分发对象。这种模式最常用于函数对象,如Collections.reverseOrder,但也适用于像Collections.emptySet这样的集合。

        假设有一个接口,描述了一个方法,该方法接受和返回某个类型T的值:

public interface UnaryFunction<T> {
 
    T apply(T arg);
 
}
           

       现在假设要提供一个恒等函数(identity function)。如果在每次需要的时候都重新创建一个,这样会很浪费,因为他是无状态的(stateless)。如果泛型被具体化了,每个类型都需要一个恒等函数,但是他们被擦除以后,就只需一个泛型单例。请看以下示例:

// Generic singleton factory pattern
 
private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>(){
 
    public Object apply(Object arg) { return arg; }
 
};
 
// IDENTITY_FUNCTION is stateless and its type parameter is
 
// unbounded so it's safe to share one instance across all types.
// 标识函数是无状态的(它在执行时不会对外界的变量、对象、数组等值进行修改。),它的类型参数是无界,//因此在所有类型中共享一个实例是安全的。
 
@SuppressWarnings("unchecked")
 
public static <T> UnaryFunction<T> identityFunction() {
 
    return (UnaryFunction<T>) IDENTITY_FUNCTION;
 
}
           

        IDENTITY_FUNCTION转换成(UnaryFunction<T>),产生了一条未受检的转换警告,因为UnaryFunction<Object>对于每个T来说并非都是个UnaryFunction<T>。但是恒等函数很特殊:它返回未被修改的参数,因此我们知道无论T的值是什么,用它作为UnaryFunction<T>都是类型安全的。因此,我们可以放心的禁止由这个转换所产生的未受检转换警告。一旦禁止,代码在编译时就不会出现任何错误或者警告。

        以下是一个范例程序,利用泛型单例作为UnaryFunction<String>和UnaryFunction<Number>。像往常一样,他不包含转换,编译时没有出现错误或者警告:

public static void main(String[] args) {
        String[] StringSet = { "a", "b", "c" };
        UnaryFunction<String> sameString = indentityFunction();
        for (String s : StringSet) {
            System.out.println(sameString.apply(s));
        }

        Number[] numbers = { 1, 2.0, 3L };
        UnaryFunction<Number> sameNumber = indentityFunction();
        for (Number n : numbers) {
            System.out.println(sameNumber.apply(n));
        }
}
           

虽然相对少见,但是通过某个包含该类型参数本身的表达式来限制类型参数是允许的,这就是递归类型限制(recursive type bound)。递归类型限制最普遍的用途与Comparable接口有关,它定义类型的自然顺序:

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

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

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

// Using a recursive type bound to express mutual comparability
 
public static <T extends Comparable<T>> T max(List<T> list) {...}
           

限制类型 <T extends Comparable<T>> ,可以读作“针对可以与自身进行比较的每个类型T”。下面的方法就带有上述声明。它根据元素的自然顺序计算列表的最大值,编译时没有出现错误或者警告:

// Returns the maximum value in a list - uses recursive type bound
 
public static <T extends Comparable<T>> T max(List<T> list) { 
    Iterator<T> i =list.iterator();
    T result = i.next(); 
    while (i.hasNext()) {
         T t = i.next(); 
        if (t.compareTo(result) > 0) result = t; 
    }
    return result; 
}
           

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

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

继续阅读