- 两种泛型处理方式:
- Code specialization。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list,可能需要 针对string,integer,float产生三份目标代码。
- Code sharing。对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。C++中的模板)是典型的Code specialization实现。
- C++编译器会为每一个泛型类实例生成一份执行代码。执行代码中integer list和string list是两种不同的类型。这样会导致代码膨胀(code bloat),不过有经验的C++程序员可以有技巧的避免代码膨胀。Code specialization另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用Code sharing方式处理泛型的主要原因。Java编译器通过Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。
- 类型擦除
- 概念:虚拟机没有泛型对象,所有对象都属于普通类。定义的任何泛型类,都自动提供了一个相应的原始类型。这个生成原始类型的过程就是类型擦除。
- 擦除过程:将所有类型参数转为该类型允许的最原始父类。比如<T>转换为Object,<T extends ClassA> 转换为ClassA,<T extends ClassA & ClassB> 转换为ClassA
- 几个擦除的例子
interface Comparable <A> { public int compareTo( A that); } //转化为
interface Comparable { public int compareTo(Comparable that); }
final class NumericValue implements Comparable <NumericValue> { private byte value; public NumericValue (byte value) { this.value = value; } public byte getValue() { return value; } public int compareTo( NumericValue t hat) { return this.value - that.value; } } // 转化为 final class NumericValue implements Comparable { priva te byte value; public NumericValue (byte value) { this.value = value; } public byte getValue() { return value; } public int compareTo( NumericValue t hat) { return this.value - that.value; } public int compareTo(Object that) { return this.compareTo((NumericValue)that); } }
class Collections { public static <A extends Comparable<A>>A max(Collection <A> xs) { Iterator <A> xi = xs.iterator(); A w = xi.next(); while (xi.hasNext()) { A x = xi.next(); if (w.compareTo(x) < 0) w = x; } return w; } } // 转化为: class Collections { public static Comparable max(Collection xs) { Iterator xi = xs.iterator(); Comparable w = (Comparable) xi.next(); while (xi.hasNext()) { Comparable x = (Comparable) xi.next(); if (w.compareTo(x) < 0) w = x; } return w; } }
final class Test { public static void main (String[ ] args) { LinkedList <NumericValue> numberList = new LinkedList <NumericValue> (); numberList .add(new NumericValue((byte)0)); numberList .add(new NumericValue((byte)1)); NumericValue y = Collections.max( numberList ); } } // 转化为 final class Test { public static void main (String[ ] args) { LinkedList numberList = new LinkedList(); numberList .add(new NumericValue((byte)0)); , numberList .add(new NumericValue((byte)1)); NumericValue y = (NumericValue) Collections.max( numberList ); } }
- 桥方法
- 考虑如下例子
public class Pair<T> { private T first=null; private T second=null; public Pair(T fir,T sec) { this.first=fir; this.second=sec; } public T getFirst() { return this.first; } public T getSecond(){ return this.second; } public void setFirst(T fir) { this.first=fir; } } // 擦除后在虚拟机中实际是如下 public class Pair { private Object first=null; private Object second=null; public Pair(Object fir,Object sec) { this.first=fir; this.second=sec; } public Object getFirst(){ return this.first; } public void setFirst(Object fir) { this.first=fir; } } // 对于一个继承Pair<T>的类: class SonPair extends Pair<String> { public void setFirst(String fir){....} } // 实际继承的是 class SonPair extends Pair { public void setFirst(Object fir){....}
}
-
所以如上的子类SonPair并没有实现setFirst(Object fir)方法,这并不是Java程序员造成的,Java编译器为了解决这个问题会在SonPair中创建一个桥方法
public void setFirst(Object fir) { setFirst((String) fir) }
- 同样若我们先覆盖父类中的getFirst方法,编译器会自动生成一个桥方法,如下:
String getFirst() // 自己定义的方法 Object getFirst() // 编译器生成的桥方法 /*编译器允许出现方法签名相同的多个方法存在于一个类中吗? 事实上,编译器是通过方法签名(方法名+参数列表)判断方法是否一样;然而存在一种灰色地带即只有编译器自己能够创建出方法签名一样而返回类型不同的方法,如果编译出了这样的方法,在执行时,JVM会用参数类型和返回类型来确定一个方法,JVM能够根据不同返回类型来确定方法签名相同的方法。*/
- 考虑如下例子
- 类型擦除引起的问题
- 用同一泛型类的实例区分方法签名?——NO!
编译器会报告冲突的函数签名,这是因为两个函数都会被擦除成public void test(List ls);import java.util.*; public class Erasure{ public void test(List<String> ls){ System.out.println("Sting"); } public void test(List<Integer> li){ System.out.println("Integer"); } }
- 同时catch同一个泛型异常类的多个实例?——NO!
- 泛型类的静态变量是共享的?——Yes!
import java.util.*; public class StaticTest{ public static void main(String[] args){ GT<Integer> gti = new GT<Integer>(); gti.var=1; GT<String> gts = new GT<String>(); gts.var=2; System.out.println(gti.var); } } class GT<T>{ public static int var=0; public void nothing(T x){} } // 答案是——2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
- 用同一泛型类的实例区分方法签名?——NO!
Java 泛型的类型擦除和桥方法
最新推荐文章于 2024-06-05 13:47:43 发布