最近在整理进入职业生涯后的技术学习路线,看到一个实现栈功能的类,贴出来,如下:
public class Stack<T> {
private static final int DEFAULT_CAPACITY = 10;
private T[] elements;
private int size;
Stack() {
elements = new T[DEFAULT_CAPACITY];
}
}
这段代码,在IDEA中会报出警告,说是不能用T直接用例实例化element,什么原因呢?
原来,因为泛型中类型擦除的特性,这段代码编译后,只知道给element实例为一个Object子类的数组,不能确定是否满足赋值兼容模式,所以IDEA提前报出此警告。
那么如何定义构造函数?
方式1:
Stack() {
elements = (T[]) new Objects[DEFAULT_CAPACITY];
}
首先用Object对象数组作为实例化的内容,此时IDE会报类型不匹配,这时再用(T[])进行强转,可满足赋值兼容模式,但这种方式不太理想,IDE依然会报“未检查的转化”警告,虽然程序可以运行,但带着这样的警告,强迫症要犯起来。
方式2:
可以通过对象工厂实例化出T类型的对象,这时可以用到JDK8提供的一个函数式接口:
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
该接口不接受任何参数,每次返回T类型的一个实例对象,这时类的结构可定义为:
public class Stack<T> {
private static final int DEFAULT_CAPACITY = 10;
private T[] elements;
private int size;
private Supplier<? extends T[]> ctor;
Stack(Supplier<? extends T[]> ctor) {
this.ctor = Objects.requireNonNull(ctor);
elements = ctor.get();
}
public static void main(String[] args) {
new Stack<>(() -> new Integer[DEFAULT_CAPACITY]);
}
}
这样可达到初始化泛型数组T的目的。
方式3:
使用class<T>,通过反射,得到此类对象的构造函数对象,通过newInstance方法返回实例对象,代码如下:
public class Stack<T> {
private static final int DEFAULT_CAPACITY = 10;
private T[] elements;
private int size;
private Constructor<? extends T[]> ctor;
Stack(Class<? extends T[]> impl) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
this.ctor = impl.getConstructor();
elements = ctor.newInstance(DEFAULT_CAPACITY);
}
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
new Stack<>(Integer[].class);
}
}
总结:
方式1有一种治标不治本的感觉,方式3用到反射,一般我们不建议用反射,因为对性能有影响,所以建议使用方式2,通过对象工厂生成对象,或者说用Builder模式,build出想要的对象,这样扩展性更好,代码也更优雅。