JavaSE_8系列博客——Java语言的特性(六)--泛型(4)--Java中泛型实现的原理
来源:互联网 发布:龙腾会计软件 编辑:程序博客网 时间:2024/06/05 06:29
类型擦除:
泛型被引入Java语言,以便在编译时提供更严格的类型检查,并支持通用编程。为了实现泛型,Java编译器将类型擦除应用于:
- 如果类型参数是无界的,则使用其边界替换通用类型中的所有类型参数或Object。因此,生成的字节码仅包含普通类,接口和方法。 (也就是说,物理上根本不存在泛型类、接口、方法)
- 如有必要,插入类型转换以保护类型安全。
- 生成桥接方法以保留扩展泛型类型中的多态。
类型擦除确保不会为参数化类型创建新类;因此,泛型不会导致运行时开销。
擦除通用类型
在类型擦除过程中,如果类型参数是有界的,则Java编译器将擦除所有类型参数,并将其替换为其第一个绑定,如果类型参数无界,则将替换为Object。 考虑以下通用类,表示单链表中的节点:
ublic class Node<T> { private T data; private Node<T> next; public Node(T data, Node<T> next) } this.data = data; this.next = next; } public T getData() { return data; } // ...}
因为类型参数T是无界的,所以Java编译器用Object替换它:
public class Node { private Object data; private Node next; public Node(Object data, Node next) { this.data = data; this.next = next; } public Object getData() { return data; } // ...}
在以下示例中,通用Node类使用有界类型参数:
public class Node<T extends Comparable<T>> { private T data; private Node<T> next; public Node(T data, Node<T> next) { this.data = data; this.next = next; } public T getData() { return data; } // ...}
Java编译器将有界类型参数T替换为第一个绑定类,Comparable:
public class Node { private Comparable data; private Node next; public Node(Comparable data, Node next) { this.data = data; this.next = next; } public Comparable getData() { return data; } // ...}
泛型方法中的类型擦除
Java编译器还可以删除泛型方法参数中的类型参数。考虑以下通用方法:
// Counts the number of occurrences of elem in anArray.//public static <T> int count(T[] anArray, T elem) { int cnt = 0; for (T e : anArray) if (e.equals(elem)) ++cnt; return cnt;}
因为T是无界的,所以Java编译器用Object替换它:
public static int count(Object[] anArray, Object elem) {
int cnt = 0;
for (Object e : anArray)
if (e.equals(elem))
++cnt;
return cnt;
}
假设定义了以下类:
class Shape { /* ... */ }class Circle extends Shape { /* ... */ }class Rectangle extends Shape { /* ... */ }
您可以编写一个通用方法来绘制不同的形状:
public static <T extends Shape> void draw(T shape) { /* ... */ }
Java编译器将T替换为Shape:
public static void draw(Shape shape) { /* ... */ }
类型擦除和桥接方法的影响
有时类型擦除会导致您可能没有预料到的情况。以下示例显示了如何发生这种情况。示例(在Bridge方法中描述)显示了编译器有时会创建一种称为桥接方法的合成方法,作为类型擦除过程的一部分。
例如:
public class Node<T> { public T data; public Node(T data) { this.data = data; } public void setData(T data) { System.out.println("Node.setData"); this.data = data; }}public class MyNode extends Node<Integer> { public MyNode(Integer data) { super(data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); }}
请考虑以下代码:
MyNode mn = new MyNode(5);Node n = mn; // A raw type - compiler throws an unchecked warningn.setData("Hello"); Integer x = mn.data; // Causes a ClassCastException to be thrown.
类型擦除后,此代码变为:
MyNode mn = new MyNode(5);Node n = (MyNode)mn; // A raw type - compiler throws an unchecked warningn.setData("Hello");Integer x = (String)mn.data; // Causes a ClassCastException to be thrown.
这时执行代码时会发生什么:
- n.setData( “Hello”);导致在MyNode类的对象上执行方法setData(Object)。(MyNode类从Node继承了setData(Object)。)
- 在setData(Object)的正文中,由n引用的对象的数据字段分配给一个String。
- 通过mn引用的同一对象的数据字段可以被访问,并且预期是一个整数(因为mn是MyNode,它是一个Node 。
- 尝试将字符串分配给整数会导致Java编译器在分配时插入的转换的ClassCastException。
桥接方法
当编译扩展参数化类或实现参数化接口的类或接口时,编译器可能需要创建称为桥接方法的合成方法,作为类型擦除过程的一部分。通常您不需要担心桥接方法,但如果出现在堆栈跟踪中,则可能会感到困惑。 类型擦除后,Node和MyNode类变为:
public class Node { public Object data; public Node(Object data) { this.data = data; } public void setData(Object data) { System.out.println("Node.setData"); this.data = data; }}public class MyNode extends Node { public MyNode(Integer data) { super(data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); }}
类型擦除后,方法签名不匹配。 Node方法变为setData(Object),MyNode方法变为setData(Integer)。因此,MyNode setData方法不会覆盖Node setData方法。
为了解决这个问题并在类型擦除之后保留通用类型的多态性,Java编译器会生成一个桥接方法,以确保子类型按预期工作。对于MyNode类,编译器为setData生成以下桥接方法:
class MyNode extends Node { // Bridge method generated by the compiler // public void setData(Object data) { setData((Integer) data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); } // ...}
正如你所看到的,在类型擦除之后,与Method类的setData方法具有相同方法签名的bridge方法委托给原始的setData方法。
泛型工作原理和重要性
理解Java中的泛型
泛型的内部原理和类型擦除带来的问题
Java官方对泛型的培训手册
- JavaSE_8系列博客——Java语言的特性(六)--泛型(4)--Java中泛型实现的原理
- JavaSE_8系列博客——Java语言的特性(六)--泛型(5)--泛型的使用
- JavaSE_8系列博客——Java语言的特性(六)--泛型(1)--宏观把控
- JavaSE_8系列博客——Java语言的特性(六)--泛型(2)--何时何地使用泛型?
- JavaSE_8系列博客——Java语言的特性(六)--泛型(3)--泛型和类型通配符
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识
- javaSE_8系列博客——Java语言的特性(四)--注解--(1)--基础知识
- javaSE_8系列博客——Java语言的特性(五)--接口和继承(3)--实现接口
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(4)-- 变量和数组
- javaSE_8系列博客——Java语言的特性(三)--类和对象(4)--声明成员变量
- javaSE_8系列博客——Java语言的特性(四)--注解--(4)-- 类型注解和可插拔类型系统
- javaSE_8系列博客——Java语言的特性(五)--接口和继承(4)--使用接口作为类型
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(1)-- 变量
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(5)-- 运算符
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(6)-- 表达式、语句、块
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(7)-- 流程控制语句
- javaSE_8系列博客——Java语言的特性(一)--关于面向对象基本概念的理解(1)
- javaSE_8系列博客——Java语言的特性(一)--关于面向对象基本概念的理解(2)--对象
- STL中vector的使用以及模拟实现
- 没有预算的新媒体运营如何启动
- python字符串
- 以指定的IE浏览器运行页面
- bootstrap 时间插件bootstrap-datetimepicker设置语言
- JavaSE_8系列博客——Java语言的特性(六)--泛型(4)--Java中泛型实现的原理
- Android主题切换(Theme)实现日夜间功能
- 天天写业务代码的程序员,怎么成为技术大牛,开始写技术代码?
- POJ 2396:Budget (有流量上下界的网络流)
- Mysql系列——数据库设计(4)——实体表之间的关系
- 448. Find All Numbers Disappeared in an Array
- 用了下itchat接口。
- java中jdk api等概念的解释
- Oracle12c 安装教程