考虑如下的方法,它的作用是返回两个集合的联合:
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() ;
利用上面的静态工厂方法,我们可以把变量声明右侧的参数类型省略掉,当参数类型多而复杂时尤其有效。
有时会需要创建不可变但是又适合于许多不同类型的对象,由于泛型是通过擦除来实现的,可以给所有的必要的类型参数使用同一个单个对象,但是需要一个静态的工厂方法来给每个必要的类型参数分发对象。这种模式叫做“泛型单例工厂”,这种模式最常用于函数对象。假设有一个接口,描述了一个方法,该方法接受和返回某个类型T的值:
package test;
public interface UnaryFunction<T> {
T apply(T t);
}
现在假设要提供一个恒等函数,如果在每次需要的时候都重新创建一个这样会很浪费,因为它是无状态的。如果泛型被具体化,那么每个类型都必须持有相应类型的桓等函数,但是在运行时擦除类型信息后,它们并没有什么区别,所以在这种情况下,只需要一个泛型单例就够了。请看以下示例:
package test;
public class diGui {
private static UnaryFunction<Object> INDENTITY_FUNCTION = new UnaryFunction<Object>() {
public Object apply(Object arg) {
return arg;
}
};
// 标识函数是无状态的(它在执行时不会对外界的变量、对象、数组等值进行修改。),它的类型参数是无界,因此在所有类型中共享一个实例是安全的。
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> indentityFunction() {
return (UnaryFunction<T>) INDENTITY_FUNCTION;
}
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));
}
}
}
通过某个包含该类型参数本身的表达式来限制类型参数是允许的,这就是递归类型限制。最普遍的用途与Comparable接口有关,它定义类型的自然顺序。许多方法都带有一个实现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> i = list.iterator();
T result = i.next();
while(i.hasNext()) {
T t = i.next();
if(t.compareTo(result) > 0) {
result = t;
}
}
return result;
}
总结,泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法来的更加安全,也更加容易。就像类型一样,你应该确保新的方法可以不用转换就能使用,这通常意味着要将它们泛型化。并且就像类型一样,还应该将现有的方法泛型化,使新用户使用起来更加轻松,且不会破坏现有的客户端。