第三十条:首选泛型方法

其实从泛型开始,这本书的难度和枯燥就开始增加,后面几节都是如此,但是!想要成为糕手必然是要承受痛苦和煎熬。

就如类可以是泛型的,方法也一样。对参数化类型进行操作的静态工具方法尤其适合于泛型化。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;  
}  

小练习:

   public static void main(String[] args) {
        Set<String> guys = Set.of("Tom", "Dick", "Harry");
        Set<String> stooges = Set.of("Larry", "Moe", "Curly");
        Set<String> union = union(guys, stooges);
        System.out.println(union);
    }

结果:

[Moe, Tom, Harry, Larry, Curly, Dick]

Process finished with exit code 0

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

我们可以用一个对象来满足所有必要的类型参数化场景,但需要编写一个静态工厂方法,以便为每个请求的类型参数化场景重复分配该对象。这种模式成为泛型单例工厂。

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

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

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

    // 泛型简单工厂模式

    private static UnaryFunction<Object> IDENTITY_FUNCTION = (t)->t;


// 标识函数是无状态的(它在执行时不会对外界的变量、对象、数组等值进行修改。),它的类型参数是无界,//因此在所有类型中共享一个实例是安全的。

    @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));
        }
}

结果:

a
b
c
1
2.0
3

Process finished with exit code 0

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

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

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

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

// 使用递归类型限定来表达可以相互比较
 
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; 
}

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

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

 所有文章无条件开放,顺手点个赞不为过吧!

                                            

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值