Java泛型之类型擦除与局限性

《Java核心技术卷一 第10版》读书笔记之Java泛型之类型擦除与局限性

以下内容参考文献包括:《java核心技术卷一 第10版》技术博客

类型擦除


要正确理解java的泛型,就需要了解类型擦除。

《java核心技术卷一》:无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased) 类型变量, 并替换为限定类型(无限定的变量用Object)。

白话:java的泛型是伪泛型,在编译期间,所有的泛型信息会被擦掉,java在生成字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

我们可以通过代码来验证这一类型擦除过程

public static void main(String[] args) {

        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(10);

        // 输出结果:true
        System.out.println(list1.getClass() == list2.getClass());
}

由上面的代码可以看出,两个ArrayList所存储的数据类型是不一样的,但是通过getClass()获取类的类型却是一样的,说明String和Integer被擦除了,变成原始类型。

这里有个概念:原始类型

原始类型:擦除了泛型信息后,在字节码中的类型变量的真正类型。

《java核心技术卷一》中对原始类型的例子

我们定义泛型类和泛型方法

class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}  

Pair会变成这样子,所有的泛型信息都被自动替换成另一种类型了,这个类型就是原始类型

class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}

《java核心技术卷一》:如果类型变量有限定,那么原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用 Object 替换。例如, 类 Pair<T>中的类型变量没有显式的限定, 因此, 原始类型用 Object 替换 T。

看下面的例子:

public class Pair<T extends Comparable & Serializable> implements Serializable {
    private T value;
    
    public Pair(T value) {
        ....
    }
}

类型擦除后,变成这样子

public class Pair implements Serializable {
    private Comparable value;
    
    public Pair(Comparable value) {
        ....
    }
}

如果限定变成这样子 class Pair<T extends Serializable & Comparable>

那么类型擦除的时候是用Serializable替换掉T的

所以,为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界列表的末尾

泛型的局限性


由上面可以知道Java的泛型是伪泛型,编译时使用了类型擦除,所以我们在使用Java泛型时需要考虑一些限制。

不能用基本类型实例化类型参数

不能用类型参数代替基本类型。因此, 没有 Pair<int>, 只 有 Pair<Integer>。 当然,其原因是类型擦除。擦除之后, Pair 类含有 Object 类型的域, 而 Object 不能存储 int值。

这的确令人烦恼。但是,这样做与 Java 语言中基本类型的独立状态相一致。这并不是一个致命的缺陷——只有 8 种基本类型, 当包装器类型(wrapper type) 不能接受替换时, 可以使用独立的类和方法处理它们。

运行时类型查询只适用于原始类型

查询一个对象是否属于某个泛型类型时,倘若使用 instanceof 会得到一个编译器错误, 如果使用强制类型转换会得到一个警告。

if (a instanceof Pair<String>)  // 编译错误
    
if (a instanceof Pair<T>)       // 编译错误
    
Pair<String> p = (Pair<String>) a;  // 警告

getClass方法总是返回原始类型,例如:

Pair<String> stringPair = . .
Pair<Employee> employeePair = . .
if (stringPair.getClass() == employeePair.getClass()) // 结果:true

两次调用getClass都返回Pair.class

不能创建参数化类型的数组

例如:

Pair<String>[] table = new Pair<String>[10]; // 编译错误

如果需要收集参数化类型对象, 只有一种安全而有效的方法:使用 ArrayList: ArrayList<Pair<String>>

不能实例化类型变量

不能使用像 new T(...)、 newT[...] 或 T.class 这样的表达式中的类型变量,例如:

public class Pair<T> {
    private T first;
    private T sencond;
    
    // Pair的构造器
    public Pair() {
        first = new T();    // 编译错误
        second = new T();   // 编译错误
    } 
}

在 Java SE 8 之后,最好的解决办法是让调用者提供一个构造器表达式。例如:

Pair<String> p = Pair.makePair(String::new);

makePair 方法接收一个 Supplier<T>,这是一个函数式接口,表示一个无参数而且返回类型为 T 的函数:

public static <T> Pair<T> makePair(Supplier<T> constr) {
    return new Pair<>(constr.get0. constr.get0);
}

或者,这么写也可以:

public static <T> Pair<T> makePair(Class<T> cl) {
    try {
        return new Pair<T>(cl.newInstance(), cl.newInstance());
    } catch(Exception e) {
        return null;
    }
}

Pair<String> p = Pair.makePair(String.class);

泛型类的静态上下文中类型变量无效

不能在静态域或方法中引用类型变量。例如:

public class Singleton<T> {
    private static T singlelnstance;        // Error
    public static T getSinglelnstance() {   // Error
        if (singleinstance == null) construct new instance of T
        return singlelnstance; 
    } 
}

类型擦除之后, 只剩下 Singleton 类,它只包含一个 singlelnstance 域。 因此, 禁止使用带有类型变量的静态域和方法。

不能抛出或捕获泛型类的实例

实际上, 甚至泛型类扩展 Throwable 都是不合法的。例如, 以下定义就不能正常编译:

// Error can't extend Throwable
public class Problem<T> extends Exception { 
    /* . . . */ 
} 

catch 子句中不能使用类型变量。例如, 以下方法将不能编译:

public static <T extends Throwable> void doWork(Class<T> t) {
    try{
        do work
    } catch (T e) {     // Error can 't catch type variable
        Logger,global.info(...) 
    } 
}

注意擦除后的冲突

当泛型类型被擦除时, 无法创建引发冲突的条件。下面是一个示例。假定像下面这样将equals 方法添加到 Pair 类中:

public class Pair<T> {
    public boolean equals(T value) { 
        return first,equals(value) && second,equals(value); 
    }
}

考虑一个 Pair<String>。从概念上讲, 它有两个 equals 方法:

boolean equals(String) // defined in Pair<T>
boolean equals(Object) // inherited from Object

但实际上,方法擦除了 boolean equals(T) 就是 boolean equals(Object),这会与Object.equals方法发送冲突。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容