java 类型擦除

类型擦除

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 类型。在 GenericsDemo06main 方法中,Point<String> 类型变为了原始类型 Pointp.getVar() 返回的对象类型为 Object,需要显式地将其转换为 String 类型,然后调用 length() 方法。

需要注意的是,不同的反编译器可能会生成略有不同的代码,但类型擦除和显式类型转换的原则是相同的。

泛型擦除有哪几种情况?

泛型擦除(Type Erasure)是 Java 编译器在编译泛型代码时所采用的一种技术。它将泛型类型信息去除,使得生成的字节码与非泛型代码相兼容。泛型擦除主要有以下几种情况:

  1. 将泛型类型参数替换为其限定类型(bounded type)或 Object:在泛型类或接口中,如果泛型类型参数没有显式地指定限定类型(如 <T extends SomeClass>),则编译器会将泛型类型参数替换为 Object 类型。如果有限定类型,则替换为相应的限定类型。

例如:


public class MyClass<T> {
    private T value;
}

经过类型擦除后,将变为:


public class MyClass {
    private Object value;
}
  1. 桥接方法(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);
    }
}
  1. 类型参数的转换:在使用泛型类型参数的地方,编译器会插入显式的类型转换代码。这是因为类型擦除后,泛型类型参数变为了 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 表达式和方法引用的类型推断。类型推断主要发生在以下几种情况:

  1. 调用泛型方法:编译器根据实参的类型推断泛型方法的类型参数。例如:

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 类型。

  1. 创建泛型实例:使用钻石操作符(<>)时,编译器根据上下文和目标类型推断泛型类型参数。例如:

List<String> list = new ArrayList<>();

在上述示例中,编译器会根据左侧的目标类型 List<String> 推断出 ArrayList 的泛型类型参数应该是 String 类型。

  1. Lambda 表达式和方法引用:编译器根据上下文和目标类型推断 lambda 表达式或方法引用的类型。例如:

List<String> list = Arrays.asList("a", "b", "c");
list.stream().map(String::toUpperCase).collect(Collectors.toList());

在上述示例中,编译器会根据 Stream.map() 方法的签名和目标类型推断出 String::toUpperCase 方法引用的类型。

类型推断的限制:

  1. 上下文不明确:当编译器无法根据上下文推断出泛型类型参数时,类型推断将失败。例如:

public static <T> void ambiguousMethod(T t1, T t2) { /* ... */ }

ambiguousMethod("a", 1); // 编译错误,因为无法推断出 T 的类型
  1. 类型擦除:运行时泛型信息会被擦除,因此类型推断仅发生在编译时。这可能导致运行时类型转换错误。
  2. 限制性的类型推断:在某些情况下,编译器可能过早地推断出泛型类型参数,导致类型检查错误。例如:

List<String> strings = Arrays.asList("a", "b", "c");
List<Object> objects = strings; // 编译错误,因为 List<String> 不是 List<Object> 的子类型

在上述示例中,尽管 StringObject 的子类型,但 List<String> 并不是 List<Object> 的子类型。因此,编译器会报错。这种情况下,可以使用通配符(?)来表示未知类型,从而提高泛型代码的灵活性。例如:


List<String> strings = Arrays.asList("a", "b", "c");
List<? extends Object> objects = strings; // 这是合法的,因为 ? extends Object 代表未知的 Object 子类型
  1. 无法推断具体类型:有时候,编译器可能无法推断出具体的泛型类型参数,这时编译器会使用类型边界(如 <T extends SomeClass>)或 Object 类型作为默认值。例如:

public static <T> T getDefaultValue() {
    return null; // 编译器允许这种情况,因为它假设 T 的类型边界是 Object
}

尽管类型推断在很多情况下可以帮助我们简化代码编写,但在某些情况下,它可能导致不易察觉的类型转换错误。因此,在使用泛型时,建议充分了解类型推断的限制,以确保编写出类型安全的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值