Effective Java学习(泛型)之——优先考虑泛型化方法

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

 

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

 

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

 此时,这个方法编译时就会出现两个警告:

 

 

       为了修复这些警告,使方法变成是类型安全的,要将方法声明修改为声明一个类型参数,表示这三个集合的元素类型,并在方法中使用类型参数,声明类型参数的类型参数列表,处在方法的修饰符及返回值之间,在这个示例中,类型参数列表为<E>,返回类型为Set<E>。类型参数的命名惯例与泛型方法以及泛型的相同:

 

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

       以上至少对于简单的泛型方法而言,就这么回事了,现在方法编译时不会产生任何警告,并提供了类型转换安全性,也更容易使用,以下是一个执行该方法的简单程序,程序中不包含转换,编译时不会有错误或者警告:

 

 

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

 结果为:[Moe, Harry, Tom, Curly, Larry, Dick],元素的顺序是依赖于实现的。

 

 

      union方法的局限性在于,三个集合的类型必须全部相同,利用有限制的通配符,可以是这个方法便得更加灵活。

 

      泛型方法的一个显著特性是,无需明确指定类型的参数值,不像调用泛型构造器的时候是必须指定的,编译器通过检查方法参数的类型来计算类型参数的值,对于上述的程序而言,编译器发现union两个参数都是Set<String>类型,因此知道类型参数<E>必须是String类型,这个过程称为类型推导(type inference)。

 

       可以利用泛型方法调用所提供的类型推导,是创建参数化类型实例的过程便得更加轻松,提醒一下:在调用泛型构造器的时候,要明确传递类型参数的值可能有点麻烦。类型参数出现在了变量声明的左右两边,显得有些冗余:

 

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

       为了消除这种冗余,可以编写一个泛型工厂方法,与想要使用的每个构造器相对应,例如,下面是一个与无参的HashMap构造器相对应的泛型静态工厂方法:

 

 

public static <K,V> HashMap<K,V> newHashMap(){

        return new HashMap<K,V>();
}

 

 

通过这种工厂方法,可以用下面这段简洁的代码来取代上面那个重复的声明

 

Map<String,List<String>> anagrams = newHashMap();

        在泛型上调用构造器时,如果语言所做的类型推导与调用泛型方法时所做的相同,那就好了,将来的某一天也许可以实现这一点,但截至到java1.6版本发行还是不行。

 

 

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

 

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

 

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

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

 

 

private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {
		@Override
		public Object apply(Object args) {
			return args;
	}
};
		
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> indetityFunction(){
	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[] strings = {"jute","hemp","nylon"};
			UnaryFunction<String> sameString = indetityFunction();
			for (String string : strings) {
				System.out.print(sameString.apply(string)+" ");
			}
			System.out.println();
			Number[] numbers = {1,2.0,3L};
			UnaryFunction<Number> sameNumber = indetityFunction();
			for (Number number : numbers) {
				System.out.print(sameNumber.apply(number)+" ");
			}
		}

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

 

 

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

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

 

 

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

 

public static <T extends Comparable<T>> T max(List<T> list){...}

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

 

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

 

public static <T extends Comparable<T>> T max(List<T> list){
			Iterator<T> iter = list.iterator();
			T result = iter.next();
			while(iter.hasNext()){
				T t = iter.next();
				if (t.compareTo(result) > 0) {
					result = t;
				}
			}
			return result;
		}

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

 

 

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

 

你可能感兴趣的:(Effective Java)