面试题答案
一键面试泛型的类型擦除机制
- 概念:Java 的泛型是在编译期实现的,这意味着在运行时,所有的泛型信息都会被擦除。编译器会将泛型类型替换为它们的原始类型(即不带类型参数的类型)。例如,
List<String>
在运行时会变成List
,所有关于String
类型的信息都丢失了。 - 原理:编译器在编译时会进行类型检查和类型推断,确保类型安全。但在生成的字节码中,泛型类型参数会被替换为其限定类型(如果有上限,例如
T extends Number
,则擦除为Number
;否则擦除为Object
)。
类型擦除对代码运行的影响
- 运行时类型丢失:由于类型擦除,在运行时无法获取泛型类型的具体信息。例如:
import java.util.ArrayList;
import java.util.List;
public class GenericTypeErasureExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
System.out.println(stringList.getClass() == integerList.getClass());
}
}
在上述代码中,运行结果为 true
,因为运行时 List<String>
和 List<Integer>
的类型都被擦除为 List
,它们的 getClass()
返回相同的结果。
- 可能导致类型转换异常:当不正确地使用泛型擦除后的代码时,可能会引发
ClassCastException
。例如:
import java.util.ArrayList;
import java.util.List;
public class TypeCastExceptionExample {
public static void main(String[] args) {
List list = new ArrayList();
list.add("Hello");
Integer i = (Integer) list.get(0);
}
}
这里,由于泛型信息被擦除,编译器无法在编译期检测到将 String
转换为 Integer
的错误,运行时就会抛出 ClassCastException
。
避免因类型擦除导致潜在问题的方法
- 使用通配符:通过使用通配符(
?
)来限制泛型类型的范围,可以在一定程度上避免类型不匹配问题。例如:
import java.util.ArrayList;
import java.util.List;
public class WildcardExample {
public static void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
List<Integer> integerList = new ArrayList<>();
integerList.add(123);
printList(stringList);
printList(integerList);
}
}
这里的 printList
方法接受任何类型的 List
,通过使用通配符避免了特定类型的限制,同时保持类型安全。
- 使用类型令牌:可以通过传递一个代表泛型类型的
Class
对象(类型令牌)来在运行时获取类型信息。例如:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
class GenericClass<T> {
private Class<T> type;
public GenericClass(Class<T> type) {
this.type = type;
}
public T createInstance() {
try {
return type.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
public class TypeTokenExample {
public static void main(String[] args) {
GenericClass<String> stringGenericClass = new GenericClass<>(String.class);
String str = stringGenericClass.createInstance();
System.out.println(str);
}
}
在这个例子中,通过传递 String.class
作为类型令牌,GenericClass
可以在运行时知道具体的泛型类型,从而创建相应类型的实例。
- 使用自限定类型:在定义泛型类或接口时,可以使用自限定类型来确保类型一致性。例如:
class SelfBounding<T extends SelfBounding<T>> {
T element;
public void setElement(T element) {
this.element = element;
}
public T getElement() {
return element;
}
}
class SubType extends SelfBounding<SubType> {
}
public class SelfBoundingExample {
public static void main(String[] args) {
SubType subType = new SubType();
subType.setElement(subType);
SubType retrieved = subType.getElement();
}
}
这里 SelfBounding
类使用自限定类型 T extends SelfBounding<T>
,确保了 SubType
只能操作自身类型,增强了类型安全性。