对Java泛型擦除的补偿
文章目录
一、泛型的擦除
在泛型代码内部,无法获得任何有关泛型参数类型的信息。
Java泛型是使用擦除实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,唯一知道的就是在使用一个对象。
1.1 为泛型类设置边界(可以设置多个边界),就可以通过泛型对象调用边界的方法
public class HasF {
public void f(){
System.out.println("HasF.f()");
}
}
class Manipulator<T extends HasF>{
private T t;
public Manipulator(T t){
this.t = t;
}
public void manipulate(){
t.f();
}
}
// 可以自行擦除,创建出没有泛型的类
class Manipulator2{
private HasF obj;
public void manipulate(){
obj.f();
}
}
当希望代码能够跨多个类工作时,使用泛型才有所帮助。例如,返回T的方法,那么泛型就有所帮助,因为它们之后将返回确切的类型:
class Manipulator3<T extends HasF>{
private T t;
public Manipulator3(T t){
this.t = t;
}
public T getT(){
return t;
}
}
二、擦除的补充
任何在运行时需要知道确切类型信息的操作都无法工作:
public class Erased<T>{
private final int SIZE = 100;
public static void f(Object arg){
if(arg instanceof T){} // error
T var = new T(); // Error
T[] array = new T[SIZE]; // Error
T[] array = (T)new Object[SIZE]; // Unchecked warning
}
}
2.1 对instanceof
的补充:引入类型标签,使用动态的isInstance()
class Building{}
class House extends Building{}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind){
this.kind = kind;
}
public boolean f(Object arg){
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<>(Building.class);
System.out.println(ctt1.f(new House()));
System.out.println(ctt1.f(new Building()));
}
}
2.2 对泛型new T()
对补偿:newInstance()
、显式工厂
使用下面的方式,如果实际类型没有默认构造器,将会运行时出错。
class Employee{}
public class ClassAsFactory<T> {
T x;
public ClassAsFactory(Class<T> kind){
try {
x = kind.newInstance();
} catch (InstantiationException|IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
ClassAsFactory<Employee> fe = new ClassAsFactory<>(Employee.class);
System.out.println("ClassAsFactory<Employee> succeed");
try {
// Integer 没有默认对构造器, 因此会失败
ClassAsFactory<Integer> fi = new ClassAsFactory<>(Integer.class);
}catch (Exception e){
System.out.println("ClassAsFactory<Integer> failed");
}
}
}
通过显式工厂,可以获得编译期检查:
interface FactoryI<T>{
T create();
}
class Foo2<T>{
private T t;
public <F extends FactoryI<T>> Foo2(F f){
t = f.create();
}
}
class IntegerFactory implements FactoryI<Integer>{
@Override
public Integer create() {
return new Integer(0);
}
}
class Widget{
public static class Factory implements FactoryI<Widget>{
@Override
public Widget create() {
return new Widget();
}
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
}
2.3 创建泛型数组:Array.newInstance()
没有任何方式可以推翻底层的数组类型,它只能是Object[]
。
class Generic<T>{}
public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic<Integer>[] gia;
public static void main(String[] args) {
// java.lang.ClassCastException
// gia = (Generic<Integer>[]) new Object[SIZE];
gia = (Generic<Integer>[])new Generic[SIZE];
System.out.println(gia.getClass().getSimpleName());
}
}
泛型T[]
,因为有了擦除,数组的运行时类型就只能是Object[]。在下面代码中调用rep()
将报错:
public class GenericArray<T> {
private T[] array;
public GenericArray(int sz){
array = (T[]) new Object[sz];
}
public T get(int index){
return array[index];
}
public T[] rep(){
return array;
}
public static void main(String[] args) {
GenericArray<Integer> gai = new GenericArray<>(10);
// java.lang.ClassCastException,因为实际运行的类型是Object[]
Integer[] ia = gai.rep();
}
}
尝试将Object[]
转换成T[]
,将在运行时产生异常。没有任何方式可以推翻底层的数组类型,它只能是Object[]
。
public class GenericArray2<T> {
private Object[] array;
public GenericArray2(int sz){
array = new Object[sz];
}
public void put(int index, T item){
array[index] = item;
}
public T get(int index){
return (T) array[index];
}
public T[] rep(){
return (T[]) array;
}
public static void main(String[] args) {
GenericArray2<Integer> gai = new GenericArray2<>(10);
for (int i = 0; i < 10; i++) {
gai.put(i, i);
}
for (int i = 0; i < 10; i++) {
System.out.print(gai.get(i) + " ");
}
System.out.println();
// java.lang.ClassCastException
Integer[] ia = gai.rep();
}
}
使用Array.newInstance()
创建泛型的数组:
public class GenericArrayWithTypeToken<T> {
private T[] array;
public GenericArrayWithTypeToken(Class<T> type, int sz){
array = (T[]) Array.newInstance(type, sz);
}
public void put(int index, T item){
array[index] = item;
}
public T[] rep(){
return array;
}
public static void main(String[] args) {
GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<>(Integer.class, 10);
Integer[] ia = gai.rep();
System.out.println(ia);
}
}