泛型既可以应用于整个类上,也可以在类中包含参数化方法,而这个方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是否是泛型没有关系。
泛型方法使得该方法能够独立于类而产生变化。以下是一个基本的指导原则:无论何时,只要你能做到,你就应该尽量使用泛型方法。对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
要定义泛型方法,只需要将泛型参数列表置于返回值之前,就像下面一样。
public class GenericMethods { public <T> void f(T t){ System.out.println(t.getClass().getName()); } public <T,U,V> void g(T t,U u,V v){ System.out.println("1:"+t.getClass().getName()+"\r2:" +u.getClass().getName()+"\r3:"+v.getClass().getName()); } public <T,U> void h(T t,U u,String v){ System.out.println("1:"+t.getClass().getName()+"\r2:" +u.getClass().getName()); } public static void main(String[] args){ GenericMethods gm=new GenericMethods(); gm.f("123"); gm.f(0.1); gm.f(0.1f); gm.f('a'); gm.f(gm); gm.g("123", 'b',0.1); gm.h(0.2, 0.4f, "123"); } }GenericMethods并不是参数化的,尽管这个类和其内部的方法可以被同时参数化,但是在这个例子中,只有f()拥有类型参数。这是由该方法的返回类型前面的类型参数列表指明的。
注意,当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找到具体的类型。这称为类型参数推断。因此,我们可以像调用普通方法一样调用f(),而且就好像f()被无限次的重载过。他甚至可以接受GenericMethods作为类型参数。如果调用f()时传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。事实上,泛型方法与自动打包避免了以前我们不得不自己编写出来的代码。
人们对类型有一个抱怨,就是有时候需要向程序中加入更多的代码,例如创建一个持有List的Map,就要向下面这样:
Map<Person,List<? extends Pet>> petPeople=new HashMap<Person,List<? extends Pet>>();
看到了吧,你在重复自己做过的事情,编译器本来应该能够从泛型参数列表中的一个参数推断出另一个参数,但是编译期目前还做不到。然而,在泛型方法中,类型参数推断可以为我们简化一部分工作。例如,我们可以编写一个工具类,它包含各种各样的static方法,专门用来创建各种常用的容器对象:
public class New { public static <K,V> Map<K,V> map(){ return new HashMap<K,V>(); } public static <T> List<T> list(){ return new ArrayList<T>(); } public static <T> LinkedList<T> lList(){ return new LinkedList<T>(); } public static <T> Queue<T> queue(){ return new LinkedList<T>(); } public static void main(String[] args) { Map<String,String> map=New.map(); List<Integer> list=New.list(); LinkedList llist=New.lList(); Queue<String> queue=New.queue(); } }Main方法演示了如何使用这个工具类,类型参数推断避免了重负的类型参数列表。对于类型参数推断而言,这是一个有趣的例子。不过,很难说他为们带了了多少好处。类型推断只对赋值操作有效,其他时候并不起作用,如果你将一个泛型方法调用的结果(例如New.map())作为参数,传递给另一个方法,这时编译器并不会执行类型推断。在这种情况下,编译器认为:调用泛型方法后,其返回值被赋值给了Object类型的变量。下面的例子证明了这一点:
public class LimitsOfInference { static void f(Map<People,List<? extends Pet>> petPeople){ } public static void main(String[] args) { //!f(New.map());无法编译 } }(2)显示的类型说明
在泛型方法中,可以显示的指明类型,不过这种语法很少使用。要显示的指明类型,必须在点操作符与方法名之间插入尖括号,然后将类型置于尖括号中。如果在定义该方法的类的内部,必须在点操作符之前使用this关键字,如果是使用了static的方法,必须在点操作符之前加上类名。使用这种语法,可以解决LimitsOfInfereace.java中的问题:
public class ExplicitTypeSpecification { static void f(Map<People,List<Pet>> petPeople){ } public static void main(String[] args) { f(New.<People,List<Pet>>map()); } }泛型方法与可变参数列表能够很好的共存:
public class GenericVarargs { public static <T> List<T> createList(T...args){ List<T> list=new ArrayList<T>(); for(T t:args) list.add(t); return list; } public static void main(String[] args) { List ls=createList("a"); System.out.println(ls); ls=createList("a","b","c"); System.out.println(ls); } }CreateList()方法展示了与标准类库中java.util.Arrays.asList()方法相同的功能。
利用生成器,我们可以很方便的填充一个Collection,而泛型化这种操作是具有实际意义的:
public class Generators { public static <T> Collection<T> fill(Collection<T> coll,Generator<T> gnr,int n){ for(int i=0;i<n;i++){ coll.add(gnr.next()); } return coll; } public static void main(String[] args) { Collection<Coffee> coll=fill(new ArrayList<Coffee>(),new CoffeeGenerator(),5); for(Coffee c:coll){ System.out.println(c); } Collection<Integer> coll2=fill(new ArrayList<Integer>(),new Fibonacci(),5); for(int i:coll2){ System.out.println(i); } } }请注意,fill()是如何透明的应用于Coffee和Integer的容器和生成器。
这个类提供了一个基本实现,用以生成某个类的对象。这个类必须具备两个特点:(1)他必须声明为public.(因为BasicGenerator与要处理的类在不同的包中,所以该类必须声明为public)(2)他必须具备默认的构造器(无参构造器)且访问权限可达。要创建这样的BasicGenerator对象,只需要调用create()方法,并传入想要生成的类型。泛型化的getInstance()方法允许执行BasicGenerator.getInstance(MyType.class),而不必执行麻烦的new BasicGenerator<MyType>(MyType.class).例如下面是一个具有默认构造器的简单的类:
public class People { private static int count=0; private final int id=count++; public String toString(){ return "People"+id; } public static void main(String[] args) { BasicGenerator<People> bs=BasicGenerator.getInstance(People.class); BasicGenerator<People> bs2=new BasicGenerator<People>(People.class);//直接调用构造器创建生成器 for(int i=0;i<5;i++){ People p=bs.next(); System.out.println(p); } } }People类能够记录它创建了多少个People实例,并通过toString()方法告诉我们其编号。我们可以看到,使用泛型方法创建Generator对象,大大减少了我们要编写的代码。Java泛型要求传入Class对象,以便也可以在getInstance()方法中对他进行类型推断。
总结:泛型方法最重要的就是利用赋值操作来进行类型推断,简化代码书写.
