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

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();
    }
}

时间: 2024-10-26 16:37:11

Java-泛型编程-类型擦除(Type Erasure)的相关文章

【转】掌握java枚举类型(enum type)

原文网址:http://iaiai.iteye.com/blog/1843553 1   背景 在java语言中还没有引入枚举类型之前,表示枚举类型的常用模式是声明一组具有int常量.之前我们通常利用public final static 方法定义的代码如下,分别用1 表示春天,2表示夏天,3表示秋天,4表示冬天. Java代码   public class Season { public static final int SPRING = 1; public static final int 

java 基本类型(primitive type)

类型 默认值 取值范围 示例 byte(Byte) 0 -2^7----2^7-1 byte b=10; char(Character) ' \u0000' 0----2^16-1 char c='c'; short(Short) 0 -2^15----2^15-1 short s=10; int(Integer) 0 -2^31----2^31-1 int i=10; long(Long) 0 -2^63----2^63-1 long o=10L; float(Float) 0.0f -2^3

swift中的&quot;类型擦除&quot;

在 Swift 的世界中,如果我们将协议称之为国王,那么泛型则可以视作皇后,所谓一山不容二虎,当我们把这两者结合起来使用的时候,似乎会遇到极大的困难.那么是否有一种方法,能够将这两个概念结合在一起,以便让它们成为我们前进道路上的垫脚石,而不是碍手碍脚的呢?答案是有的,这里我们将会使用到类型擦除 (Type Erasure) 这个强大的特性. Protocol 'SpellDelegate' can only be used as a generic constraint because it h

转:有关Java泛型的类型擦除(type erasing)

转载自:拈花微笑 自从Java 5引入泛型之后,Java与C++对于泛型不同的实现的优劣便一直是饭后的谈资.在我之前的很多training中,当讲到Java泛型时总是会和C++的实现比较,一般得出的结论是 Java使用类型擦除(type erasing),泛型信息只在编译时供javac作类型检查用,在编译后便被javac擦除,因此无法被反射 C++使用代码模板实现泛型,即在预处理时会生成类似?list_int?,?list_char?等的泛型类,虽然解决Java的运行时伪泛型的问题,但是会导致编

[运行时获取模板类类型] Java 反射机制 + 类型擦除机制

给定一个带模板参数的类 class A<T> { } 如何在运行时获取 T的类型? 在C#中,这个很简单,CLR的反射机制是解释器支持的,大概代码为: namespace TestReflect { class Program<T> { public Type getTClass() { Type type= this.GetType(); Type[] tts = type.GetGenericArguments(); return tts[0]; } } } 可是在Java中,

Java——协变数组和类型擦除(covariant array ; type erasure)

1.数组的协变性 数组的协变性(covariant)是指: 如果类Base是类Sub的基类,那么Base[]就是Sub[]的基类. 而泛型是不可变的(invariant),List不会是List的基类,更不会是它的子类. 数组的协变性可能会导致一些错误,比如下面的代码: 1 2 3 4 public static void main(String[] args) {     Object[] array = new String[10];     array[0] = 10; } 它是可以编译通

java类型擦除(Java Type Erasure Mechanism)

在JDK5之后java提供了泛型(Java Genertics),允许在定义类的时候使用类型作为参数.泛型广泛应用于各类集合中.本文对其以及其用法进行介绍. 1.一个常见的错误 下面例子中,用List<Object>类型的参数来接收List<String>. public class Main { public static void main(String[] args) throws IOException { ArrayList<String> al = new

Type Erasure with Pokemon---swift的类型擦除

我感觉这个是swift的设计缺陷. 类型擦除:解决泛型类型作为公用类型的问题 是抽象的公用机制的一种实现方式. 1)类型擦除并不能解决类型不一致的兼容问题,只能解决类似继承一致性的兼容问题. 2)擦除类型后的抽象类型,其类型信息仍然存在,只是需要保持逻辑一致性. import Foundation protocol Pokemon { associatedtype Power func attack() -> Power } struct Pikachu: Pokemon { func atta

java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题

java泛型(二).泛型的内部原理:类型擦除以及类型擦除带来的问题 参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首要前提是理解类型擦出(type erasure). Java中的泛型基本上都是在编译器这个层次来实现的.在生成的Java字节码中是不包含泛型中的类型信息的.使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉.这个过程就称为类型