设计模式——工厂方法&模版方法
Java的泛型一直是我比较感兴趣的部分,但是既然说起泛型,就不得不提到擦除。Java泛型是使用擦除实现的,使用泛型时,具体的类型信息都被“擦除”了。举个例子:List<String>和List<Integer>在运行时实际上都是相同的类型,都被擦除成了“原生的”类型,即List。
泛型类型参数将擦除到它的第一个边界,如List<T>将被擦除成List,而普通的类型变量在未指定边界的情况下被擦除成Object。
1 package com.test.generic; 2 3 public class Erased<T> { 4 private final int SIZE = 100; 5 public static void f(Object arg) { 6 if(arg instanceof T) {} //编译报错 7 T var = new T(); //编译报错 8 T[] array = new T[SIZE]; //编译报错 9 T[] array = new Onject[SIZE]; //编译报错 10 } 11 }
工厂方法
在上面的代码中,创建一个new T()的操作无法实现,部分原因就是擦除,另一部分原因是因为编译器不能验证码T具有默认(无参)的构造器。Java的解决方案是传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象,如下代码所示:
1 package com.test.generic; 2 3 class ClassAsFactory<T> { 4 T x; 5 public ClassAsFactory(Class<T> kind) { 6 try { 7 x = kind.newInstance(); 8 } catch (Exception e) { 9 throw new RuntimeException(e); 10 } 11 } 12 } 13 14 class Employee {} 15 16 public class InstantiateGenericType { 17 18 public static void main(String[] args) { 19 ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(Employee.class); 20 System.out.println("ClassAsFactory<Employee> succeeded"); 21 22 try { 23 ClassAsFactory<Integer> fi = new ClassAsFactory<Integer>(Integer.class); 24 } catch (Exception e) { 25 System.out.println("ClassAsFactory<Employee> failed"); 26 } 27 } 28 29 }
运行结果如下
从运行结果看到,可以编译,但是会因为ClassAsFactory<integer>而失败,因为Integer没有任何默认的构造器。因为这个错误不是在编译期捕获的。Sun公司建议使用显式的工厂,并限制类型,只能接受实现了这工厂的类,如下代码所示:
1 package com.test.generic; 2 3 interface FactoryI<T> { 4 T create(); 5 } 6 7 class Foo2<T> { 8 private T x; 9 public <F extends FactoryI<T>> Foo2(F factory) { 10 x = factory.create(); 11 } 12 } 13 14 class IntegerFactory implements FactoryI<Integer> { 15 public Integer create() { 16 return new Integer(0); 17 } 18 } 19 20 class Widget { 21 public static class Factory implements FactoryI<Widget> { 22 @Override 23 public Widget create() { 24 return new Widget(); 25 } 26 } 27 } 28 public class FactoryConstraint { 29 public static void main(String[] args) { 30 new Foo2<Integer>(new IntegerFactory()); 31 new Foo2<Widget>(new Widget.Factory()); 32 } 33 }
Class<T>碰巧是创建内建的工厂对象,而通过上面那样显示的创建一个工厂对象,可以获得编译期的检查。
模版方法:
另一种方法是使用模版方法设计模式,通过实现抽象类(模版)的抽象方法来创建对象,同样能获得编译期的类型检查,下面是代码:
1 package com.test.generic; 2 3 abstract class GenericWithCreate<T> { 4 final T element; 5 GenericWithCreate() { 6 element = create(); 7 } 8 abstract T create(); 9 } 10 11 class X {} 12 13 class Creator extends GenericWithCreate<X> { 14 @Override 15 X create() { 16 return new X(); 17 } 18 void f() { 19 System.out.println(element.getClass().getSimpleName()); 20 } 21 } 22 public class CreatorGeneric { 23 public static void main(String[] args) { 24 Creator c = new Creator(); 25 c.f(); 26 } 27 }
总结:
在使用泛型的过程中,因为擦除的存在,任何在运行时需要知道确切类型信息的操作都没法好好工作。通过反射配合工厂方法或者模版方法的使用,我们能在编译器就知道创建对象时传递的参数类型是否正确。