泛型编程-类型擦除(Type Erasure) Java为例子

Java中的泛型代码和C++中的模板有一个很大的不同:C++中模板的实例化会为每一种类型都产生一套不同的代码,这就是所谓的代码膨胀。
Java中并不会产生这个问题。虚拟机中并没有泛型类型对象,所有的对象都是普通类。

虚拟机中的泛型转换需要记住4条事实:
1) 定义任何一个泛型都会自动产生其原始类型(raw type)
2) 这个过程中,泛型类型会被擦除,替换为其限定类型(bounding type)
3) 必要时插入强制转换来保证类型安全
4) 使用桥接方法(bridge method)来保证正确处理多态

例如写一个返回数组中最小值的泛型函数,可以如下定义。

public static <T extends Comparable> T min(T[] a)

实际上Comparable也是一个泛型接口,所以最正确的定义方法是

public static <T extends Comparable<? super T>> T min(T[] a) 

类型擦除后,替换为限定类型Comparable,其原始类型就是

public static Comparable min(Comparable[] a)

如果没有限定类型,那么类型擦除后就替换为Object。例如一个Pair<T>类

public class Pair<T> {
    private T first;
    private T second;
 
    public Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }
 
    public T getFirst() {
        return first;
    }
 
    public T getSecond() {
        return second;
    }
 
    public void setFirst(T newValue) {
        first = newValue;
    }
 
    public void setSecond(T newValue) {
        second = newValue;
    }
}

Pair<T>类执行类型擦除后的原始类型就是

public class Pair {
    private Object first;
    private Object second;
 
    public Pair(Object first, Object second) {
        this.first = first;
        this.second = second;
    }
 
    public Object getFirst() {
        return first;
    }
 
    public Object getSecond() {
        return second;
    }
 
    public void setFirst(Object newValue) {
        first = newValue;
    }
 
    public void setSecond(Object newValue) {
        second = newValue;
    }
}

下面一段代码,
Pair<String> pair = new Pair<String>("1", "2");
String s = pair.getFirst();
类型擦除后,getFirst()返回Object。所以,编译器实际上把它转换为下面的虚拟机指令:
a) 调用原始类型的Pair.getFirst
b) 把返回的Object对象强制转换为String


类型擦除有时候会带来很复杂的情况:
例如下面写了一个新的类:

class DateInterval extends Pair<Date> {
    public DateInterval(Date first, Date second) {
        super(first, second);
    }
    
    public void setSecond(Date second) {
        if (second.compareTo(getFirst()) >= 0) {
            super.setSecond(second);
        } else {
            throw new IllegalArgumentException("Second date should be no earlier than first date.");
        }
    }
    
    public Date getSecond() {
        return (Date) super.getSecond().clone();
    }
}

类型擦除后,DateInterval中就有两个setSecond方法。
public void setSecond(Object second)
public void setSecond(Date second)
在这里,类型擦除和多态就产生了冲突。因为实际上我们是想要覆盖基类的方法。
为了解决这类问题,编译器会产生一个桥接方法
public void setSecond(Object second) {
setSecond((Date) second);
}

类型擦除后,DateInterval中有两个getSecond方法
public Date getSecond()
public Object getSecond()
在Java代码中,直接这么申明两个方法,是会导致编译错误的,因为不允许两个同名方法具有相同的参数类型。
但是,在虚拟机中,也就是编译后的代码中,参数类型加上返回类型来识别一个方法,所以这样做是允许的,因为返回类型不一样。
实际上,编译器生成这样一个桥接方法。
public Object getSecond() {
return getSecond();
}

可以使用Jad工具来反编译class文件,就能清除到看到编译器所做的工作。
例如运行jad.exe DateInterval.class,生成下面的反编译文件

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   Pair.java
 
package learning.generic;
 
import java.util.Date;
 
// Referenced classes of package learning.generic:
//            Pair
 
class DateInterval extends Pair
{
 
    public DateInterval(Date first, Date second)
    {
        super(first, second);
    }
 
    public void setSecond(Date second)
    {
        if(second.compareTo((Date)getFirst()) >= 0)
            super.setSecond(second);
        else
            throw new IllegalArgumentException("Second date should be no earlier than first date.");
    }
 
    public Date getSecond()
    {
        return (Date)((Date)super.getSecond()).clone();
    }
 
    public volatile void setSecond(Object obj)
    {
        setSecond((Date)obj);
    }
 
    public volatile Object getSecond()
    {
        return getSecond();
    }
}

参考连接:

Java-泛型编程-类型擦除(Type Erasure)

 

 
posted @ 2022-07-21 23:17  余生以学  阅读(112)  评论(0编辑  收藏  举报