惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Cisco Blogs
T
The Exploit Database - CXSecurity.com
G
GRAHAM CLULEY
AI
AI
腾讯CDC
月光博客
月光博客
P
Privacy International News Feed
aimingoo的专栏
aimingoo的专栏
博客园 - 聂微东
Latest news
Latest news
宝玉的分享
宝玉的分享
The Last Watchdog
The Last Watchdog
H
Hacker News: Front Page
博客园 - 【当耐特】
H
Hackread – Cybersecurity News, Data Breaches, AI and More
G
Google Developers Blog
D
Docker
D
DataBreaches.Net
O
OpenAI News
IT之家
IT之家
T
Threat Research - Cisco Blogs
S
Securelist
S
Secure Thoughts
V
Visual Studio Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
A
Arctic Wolf
I
Intezer
Simon Willison's Weblog
Simon Willison's Weblog
L
Lohrmann on Cybersecurity
C
Cyber Attacks, Cyber Crime and Cyber Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Tor Project blog
Google DeepMind News
Google DeepMind News
J
Java Code Geeks
Help Net Security
Help Net Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
N
Netflix TechBlog - Medium
F
Fortinet All Blogs
Attack and Defense Labs
Attack and Defense Labs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - Franky
C
Check Point Blog
美团技术团队
Hugging Face - Blog
Hugging Face - Blog
罗磊的独立博客
Forbes - Security
Forbes - Security
F
Full Disclosure
S
Security @ Cisco Blogs
B
Blog RSS Feed

博客园 - everest33

计算机 ClickHouse原理解析和应用实践学习笔记 图床 HTTP学习笔记 机器学习学习记录 squid学习笔记 VMware平时记录 摄影随记 IDEA vim配置(.vimrc文件) 分布式系统 杂记 Dockerfile、Docker镜像和Docker容器的关系 Java多线程学习笔记 正则表达式学习记录 Aspose 学习记录 ELK 学习笔记 Java泛型之通配符 shell中 >/dev/null 2>&1 是什么意思 Visual Studio Code 学习记录
Java中的类型擦除与桥方法
everest33 · 2019-09-22 · via 博客园 - everest33

类型擦除

Java在语法中虽然存在泛型的概念,但是在虚拟机中却没有泛型的概念,虚拟机中所有的类型都是普通类。无论何时定义一个泛型类型,编译后类型会被都被自动转换成一个相应的原始类型。

比如这个类

public class Parent<T>
{
    public void sayHello(T value)
    {
        System.out.println("This is Parent Class, value is " + value);
    }
}

在编译后就变成了

public class Parent
{
    public void sayHello(Object value)
    {
        System.out.println("This is Parent Class, value is " + value);
    }
}

对类型变量进行替换的规则有两条:

  • 若为无限定的类型,如<T>,被替换为Object
  • 若为限定类型,如<T extends Comparable & Serializable>,则用第一个限定的类型变量来替换,在这里被替换为Comparable

桥方法

类型擦除后,就产生了一个奇怪的现象。

假设有一个超类:

public class Parent<T>
{
    public void sayHello(T value)
    {
        System.out.println("This is Parent Class, value is " + value);
    }
}

以及一个子类:

public class Child extends Parent<String>
{
    public void sayHello(String value)
    {
        System.out.println("This is Child class, value is " + value);
    }
}

最后有以下测试代码,企图实现多态:

public class MainApp
{
    public static void main(String[] args)
    {
        Child child = new Child();
        Parent<String> parent = child;

        parent.sayHello("This is a string");
    }
}

运行的时候,会对Child类的方法表进行搜索,先分析一下Child类的方法表里有哪些东西:

1. sayHello(Object value) : 从类型被擦除后的超类中继承过来
2. sayHello(String value) : 自己新增的方法,和超类毫无联系
3. 一些从Object类继承来的方法,这里忽略

按理来说,这段测试代码应该不能通过编译,因为要实现多态的话,所调用的方法必须在子类中重写,但是在这里Child类并没有重写Parent类中的sayHello(Object value)方法,只是单纯的继承而已,并且新加了一个参数不同的同名方法。

【自己补充理解】

  • sayHello(Object value)和sayHello(String value)是两个完全不同的签名方法,没有任何关系。但是如果没有sayHello(String)则调用sayHello("this is string")时则会调用sayHello(Object)签名的方法。
  • 原文中说不能通过编译,实际上应该是可以的,只是不再是多态而已。子类没有重写父类方法,指向子类对象的父类引用依然可以调用父类自己的方法,只是此时不叫多态。

【自己补充理解完毕】

但是结果是可以正常运行。

原因是编译器在Child类中自动生成了一个桥方法

public void sayHello(Object value)
{
    sayHello((String) value);
}

可以看出,这个桥方法实际上就是对超类中sayHello(Obejct)的重写。这样做的原因是,当程序员在子类中写下以下这段代码的时候,本意是对超类中的同名方法进行重写,但因为超类发生了类型擦除,所以实际上并没有重写成功,因此加入了桥方法的机制来避免类型擦除与多态发生冲突。

public class Child extends Parent<String>
{
    public void sayHello(String value)
    {
        System.out.println("This is Child class, value is " + value);
    }
}

桥方法并不需要自己手动生成,一切都是编译器自动完成的。

桥方法与Geter

同样的,如果超类中有getter的话,在使用多态的时候也可能发生冲突。假设有超类被类型擦除后存在这样一个方法:

Obejct getValue()

然后在子类中,程序员想要重写这个方法,因此新增了一个这样的方法:

String getValue()

但是正如前面所述,重写并没有起作用,甚至还应该报错,因为在子类中,根据 函数签名=方法名+参数 的原则,从超类继承的方法与新增的方法冲突了。

但实际上这样的代码是可以工作的,原因在于,JVM是用返回值+方法名+参数的方式来计算函数签名的,所以编译器就可以借助这一原则来生成一个桥方法。不过这种计算函数签名的方法仅仅存在于虚拟机中。