类型擦除
class Point{ // 此处可以随便写标识符号,T是type的简称
private T var ; // var的类型由T指定,即:由外部指定
public T getVar(){ // 返回值的类型由外部决定
return var ;
}
public void setVar(T var){ // 设置的类型也由外部决定
this.var = var ;
}
}
public class GenericsDemo06{
public static void main(String args[]){
Point p = new Point() ; // 里面的var类型为String类型
p.setVar(“it”) ; // 设置字符串
System.out.println(p.getVar().length()) ; // 取得字符串的长度
}
}
下面的
public class GenericsDemo06{
public static void main(String args[]){
Point p = new Point() ; // 里面的var类型为String类型
p.setVar(“it”) ; // 设置字符串
System.out.println(p.getVar().length()) ; // 取得字符串的长度
}
}
反编译会变成什么样子?
当你编译并运行上述代码后,JVM 会使用类型擦除(Type Erasure)技术将泛型转换为原始类型。类型擦除是 Java 泛型实现的一部分,它在编译时去除泛型信息,使得生成的字节码与非泛型代码相兼容。
反编译后的代码可能如下:
class Point {
private Object var;
public Object getVar() {
return var;
}
public void setVar(Object var) {
this.var = var;
}
}
public class GenericsDemo06 {
public static void main(String[] args) {
Point p = new Point();
p.setVar("it");
System.out.println(((String) p.getVar()).length());
}
}
在反编译后的代码中,可以看到泛型 T
被替换为了 Object
类型。在 GenericsDemo06
的 main
方法中,Point<String>
类型变为了原始类型 Point
,p.getVar()
返回的对象类型为 Object
,需要显式地将其转换为 String
类型,然后调用 length()
方法。
需要注意的是,不同的反编译器可能会生成略有不同的代码,但类型擦除和显式类型转换的原则是相同的。
泛型擦除有哪几种情况?
泛型擦除(Type Erasure)是 Java 编译器在编译泛型代码时所采用的一种技术。它将泛型类型信息去除,使得生成的字节码与非泛型代码相兼容。泛型擦除主要有以下几种情况:
- 将泛型类型参数替换为其限定类型(bounded type)或 Object:在泛型类或接口中,如果泛型类型参数没有显式地指定限定类型(如
<T extends SomeClass>
),则编译器会将泛型类型参数替换为 Object 类型。如果有限定类型,则替换为相应的限定类型。
例如:
public class MyClass<T> {
private T value;
}
经过类型擦除后,将变为:
public class MyClass {
private Object value;
}
- 桥接方法(Bridge Method)的生成:当一个泛型类继承或实现一个泛型接口时,由于类型擦除的存在,可能导致方法签名的冲突。为解决这个问题,编译器会生成桥接方法,保证多态性和类型安全。
例如:
public interface Comparable<T> {
int compareTo(T other);
}
public class MyClass implements Comparable<MyClass> {
@Override
public int compareTo(MyClass other) {
// ...
}
}
经过类型擦除后,编译器将生成一个桥接方法,类似于以下形式:
public class MyClass implements Comparable {
public int compareTo(MyClass other) {
// ...
}
// 桥接方法
public int compareTo(Object other) {
return compareTo((MyClass) other);
}
}
- 类型参数的转换:在使用泛型类型参数的地方,编译器会插入显式的类型转换代码。这是因为类型擦除后,泛型类型参数变为了 Object 类型或限定类型,需要在运行时进行正确的类型转换。
例如:
public class MyClass<T> {
private T value;
public T getValue() {
return value;
}
}
MyClass<String> obj = new MyClass<>();
String value = obj.getValue();
经过类型擦除后,将变为:
public class MyClass {
private Object value;
public Object getValue() {
return value;
}
}
MyClass obj = new MyClass();
String value = (String) obj.getValue();
总的来说,泛型擦除涉及到泛型类型参数的替换、桥接方法的生成以及类型参数的转换。这些情况都是为了在保证多态性、类型安全和代码兼容性的前提下实现泛型功能。
使用 的情况下呢?
当泛型类型参数显式地指定了限定类型,如 <T extends SomeClass>
,编译器在进行泛型擦除时会将泛型类型参数替换为限定类型(SomeClass)。
例如,我们有一个简单的泛型类:
public class MyClass<T extends Number> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
在编译时,编译器会进行泛型擦除,将泛型类型参数 T 替换为限定类型 Number:
public class MyClass {
private Number value;
public Number getValue() {
return value;
}
public void setValue(Number value) {
this.value = value;
}
}
这样,在使用这个类时,我们需要在运行时进行类型转换,以保证类型安全。例如:
MyClass<Integer> obj = new MyClass<>();
obj.setValue(42);
Integer value = (Integer) obj.getValue();
请注意,在这种情况下,类型擦除后的类会保留限定类型的信息。这意味着你只能为 MyClass 类提供 Number 类及其子类的实例。这有助于在编译时检查类型安全性。然而,运行时的泛型信息仍然会被擦除。
类型推断
类型推断是 Java 编译器根据上下文和实参自动确定泛型类型参数的过程。类型推断发生在编译时,其目标是提高类型安全性、减少类型转换错误,并简化代码编写。Java 7 引入了钻石操作符(<>
)以简化类型推断,Java 8 引入了对 lambda 表达式和方法引用的类型推断。类型推断主要发生在以下几种情况:
- 调用泛型方法:编译器根据实参的类型推断泛型方法的类型参数。例如:
public <T> T getValue(T[] array, int index) {
return array[index];
}
String[] strings = {
"a", "b", "c"};
String value = getValue(strings, 1);
在上述示例中,编译器会根据实参 strings
(类型为 String[]
)推断出泛型类型参数 T
应该是 String
类型。
- 创建泛型实例:使用钻石操作符(
<>
)时,编译器根据上下文和目标类型推断泛型类型参数。例如:
List<String> list = new ArrayList<>();
在上述示例中,编译器会根据左侧的目标类型 List<String>
推断出 ArrayList
的泛型类型参数应该是 String
类型。
- Lambda 表达式和方法引用:编译器根据上下文和目标类型推断 lambda 表达式或方法引用的类型。例如:
List<String> list = Arrays.asList("a", "b", "c");
list.stream().map(String::toUpperCase).collect(Collectors.toList());
在上述示例中,编译器会根据 Stream.map()
方法的签名和目标类型推断出 String::toUpperCase
方法引用的类型。
类型推断的限制:
- 上下文不明确:当编译器无法根据上下文推断出泛型类型参数时,类型推断将失败。例如:
public static <T> void ambiguousMethod(T t1, T t2) {
/* ... */ }
ambiguousMethod("a", 1); // 编译错误,因为无法推断出 T 的类型
- 类型擦除:运行时泛型信息会被擦除,因此类型推断仅发生在编译时。这可能导致运行时类型转换错误。
- 限制性的类型推断:在某些情况下,编译器可能过早地推断出泛型类型参数,导致类型检查错误。例如:
List<String> strings = Arrays.asList("a", "b", "c");
List<Object> objects = strings; // 编译错误,因为 List<String> 不是 List<Object> 的子类型
在上述示例中,尽管 String
是 Object
的子类型,但 List<String>
并不是 List<Object>
的子类型。因此,编译器会报错。这种情况下,可以使用通配符(?
)来表示未知类型,从而提高泛型代码的灵活性。例如:
List<String> strings = Arrays.asList("a", "b", "c");
List<? extends Object> objects = strings; // 这是合法的,因为 ? extends Object 代表未知的 Object 子类型
- 无法推断具体类型:有时候,编译器可能无法推断出具体的泛型类型参数,这时编译器会使用类型边界(如
<T extends SomeClass>
)或Object
类型作为默认值。例如:
public static <T> T getDefaultValue() {
return null; // 编译器允许这种情况,因为它假设 T 的类型边界是 Object
}
尽管类型推断在很多情况下可以帮助我们简化代码编写,但在某些情况下,它可能导致不易察觉的类型转换错误。因此,在使用泛型时,建议充分了解类型推断的限制,以确保编写出类型安全的代码。
文章评论