详解Java 中泛型的实现原理

 更新时间:2021年03月03日 14:41:12   作者:robothy  
这篇文章主要介绍了详解Java 中泛型的实现原理,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下

泛型是 Java 开发中常用的技术,了解泛型的几种形式和实现泛型的基本原理,有助于写出更优质的代码。本文总结了 Java 泛型的三种形式以及泛型实现原理。

泛型

泛型的本质是对类型进行参数化,在代码逻辑不关注具体的数据类型时使用。例如:实现一个通用的排序算法,此时关注的是算法本身,而非排序的对象的类型。

泛型方法

如下定义了一个泛型方法, 声明了一个类型变量,它可以应用于参数,返回值,和方法内的代码逻辑。

class GenericMethod{
 public <T> T[] sort(T[] elements){
  return elements;
 }
}

泛型类

与泛型方法类似,泛型类也需要声明类型变量,只不过位置放在了类名后面,作用的范围包括了当前中的成员变量类型,方法参数类型,方法返回类型,以及方法内的代码中。

子类继承泛型类时或者实例化泛型类的对象时,需要指定具体的参数类型或者声明一个参数变量。如下,SubGenericClass 继承了泛型类 GenericClass,其中类型变量 ID 的值为 Integer,同时子类声明了另一个类型变量 E,并将E 填入了父类声明的 T 中。

class GenericClass<ID, T>{
 
}

class SubGenericClass<T> extends GenericClass<Integer, T>{
 
}

泛型接口

泛型接口与泛型类类似,也需要在接口名后面声明类型变量,作用于接口中的抽象方法返回类型和参数类型。子类在实现泛型接口时需要填入具体的数据类型或者填入子类声明的类型变量。

interface GenericInterface<T> {
 T append(T seg);
}

泛型的基本原理

泛型本质是将数据类型参数化,它通过擦除的方式来实现。声明了泛型的 .java 源代码,在编译生成 .class 文件之后,泛型相关的信息就消失了。可以认为,源代码中泛型相关的信息,就是提供给编译器用的。泛型信息对 Java 编译器可以见,对 Java 虚拟机不可见。

Java 编译器通过如下方式实现擦除:

  • 用 Object 或者界定类型替代泛型,产生的字节码中只包含了原始的类,接口和方法;
  • 在恰当的位置插入强制转换代码来确保类型安全;
  • 在继承了泛型类或接口的类中插入桥接方法来保留多态性。

Java 官方文档原文

Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
Insert type casts if necessary to preserve type safety.
Generate bridge methods to preserve polymorphism in extended generic types.

下面通过具体代码来说明 Java 中的类型擦除。

实验原理:先用 javac 将 .java 文件编译成 .class 文件,再使用反编译工具 jad 将 .class 文件反编成回 Java 代码,反编译出来的 Java 代码内容反映的即为 .class 文件中的信息。

如下源代码,定义 User 类,实现了 Comparable 接口,类型参数填入 User,实现 compareTo 方法。

class User implements Comparable<User> {
 String name;
	
 public int compareTo(User other){
  return this.name.compareTo(other.name);
 }
}

JDK 中 Comparable 接口源码内容如下:

package java.lang;
public interface Comparable<T>{
 int compareTo(T o);
}

我们首先反编译它的接口,Comparable 接口的字节码文件,可以在 $JRE_HOME/lib/rt.jar 中找到,将它复制到某个目录。使用 jad.exe(需要另外安装)反编译这个 Comparable.class 文件。

$ jad Comparable.class

反编译出来的内容放在 Comparable.jad 文件中,文件内容如下:

// 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: Comparable.java

package java.lang;

// Referenced classes of package java.lang:
//   Object

public interface Comparable
{

 public abstract int compareTo(Object obj);
}

对比源代码 Comparable.java 和反编译代码 Comparable.jad 的内容不难发现,反编译之后的内容中已经没有了类型变量 T 。compareTo 方法中的参数类型 T 也被替换成了 Object。这就符合上面提到的第 1 条擦除原则。这里演示的是用 Object 替换类型参数,使用界定类型替换类型参数的例子可以反编译一下 Collections.class 试试,里面使用了大量的泛型。

使用 javac.exe 将 User.java 编译成 .class 文件,然后使用 jad 将 .class 文件反编译成 Java 代码。

$ javac User.java
$ jad User.class

User.jad 文件内容如下:

// 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: User.java


class User
 implements Comparable
{

 User()
 {
 }

 public int compareTo(User user)
 {
  return name.compareTo(user.name);
 }

 // 桥接方法
 public volatile int compareTo(Object obj)
 {
  return compareTo((User)obj);
 }

 String name;
}

对比编辑的源代码 User.java 和反编译出来的代码 User.jad,容易发现:类型参数没有了,多了一个无参构造方法,多了一个 compareTo(Object obj) 方法,这个就是桥接方法,还可以发现参数 obj 被强转成 User 再传入 compareTo(User user) 方法。通过这些内容可以看到擦除规则 2 和规则 3 的实现方式。

强转规则比较好理解,因为泛型被替换成了 Object,要调用具体类型的方法或者成员变量,当然需要先强转成具体类型才能使用。那么插入的桥接方法该如何理解呢?

如果我们只按照下面方式去使用 User 类,这样确实不需要参数类型为 Object 的桥接方法。

User user = new User();
User other = new User();
user.comparetTo(other);

但是,Java 中的多态特性允许我们使用一个父类或者接口的引用指向一个子类对象。

Comparable<User> user = new User();

而按照 Object 替换泛型参数原则,Comparable 接口中只有 compareTo(Object) 方法,假设没有桥接方法,显然如下代码是不能运行的。所以 Java 编译器需要为子类(泛型类的子类或泛型接口的实现类)中使用了泛型的方法额外生成一个桥接方法,通过这个方法来保证 Java 中的多态特性。

Comparable<User> user = new User();
Object other = new User();
user.compareTo(other);

而普通类中的泛型方法在进行类型擦除时不会产生桥接方法。例如:

class Dog{
 <T> void eat(T[] food){
 }
}

类型擦除之后变成了:

class Dog
{

 Dog()
 {
 }

 void eat(Object aobj[])
 {
 }
}

小结

Java 中的泛型有 3 种形式,泛型方法,泛型类,泛型接口。Java 通过在编译时类型擦除的方式来实现泛型。擦除时使用 Object 或者界定类型替代泛型,同时在要调用具体类型方法或者成员变量的时候插入强转代码,为了保证多态特性,Java 编译器还会为泛型类的子类生成桥接方法。类型信息在编译阶段被擦除之后,程序在运行期间无法获取类型参数所对应的具体类型。

参考

https://docs.oracle.com/javase/tutorial/java/generics/index.html

https://stackoverflow.com/questions/25040837/generics-bridge-method-on-polymorphism

以上就是详解Java 中泛型的实现原理的详细内容,更多关于Java 泛型实现原理的资料请关注脚本之家其它相关文章!

相关文章

  • spring mvc中@PathVariable / 带斜杠方式获取

    spring mvc中@PathVariable / 带斜杠方式获取

    这篇文章主要介绍了spring mvc中@PathVariable / 带斜杠方式获取,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • SpringBoot中获取微信用户信息的方法

    SpringBoot中获取微信用户信息的方法

    这篇文章主要介绍了SpringBoot中获取微信用户信息的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • Servlet实现简单的用户登录功能实例代码

    Servlet实现简单的用户登录功能实例代码

    这篇文章主要给大家介绍了关于利用Servlet实现简单的用户登录功能的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • Java中的BigDecimal精度运算详解

    Java中的BigDecimal精度运算详解

    这篇文章主要介绍了Java中的BigDecimal精度运算详解,Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算,双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理,需要的朋友可以参考下
    2023-10-10
  • Spring中的BeanFactory对象实例化工厂详解

    Spring中的BeanFactory对象实例化工厂详解

    这篇文章主要介绍了Spring中的BeanFactory对象实例化工厂详解,BeanFactory及其子类是Spring IOC容器中最重要的一个类,BeanFactory由类名可以看出其是一个Bean工厂类,其实它确实是一个Bean工厂类,完成Bean的初始化操作,需要的朋友可以参考下
    2023-12-12
  • 详解Java中static关键字和内部类的使用

    详解Java中static关键字和内部类的使用

    这篇文章主要为大家详细介绍了Java中static关键字和内部类的使用,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-08-08
  • IntelliJ IDEA之高效代码插件RainBow Brackets详解

    IntelliJ IDEA之高效代码插件RainBow Brackets详解

    这篇文章主要介绍了IntelliJ IDEA之高效代码插件RainBow Brackets详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • Java字符流与字节流区别与用法分析

    Java字符流与字节流区别与用法分析

    这篇文章主要介绍了Java字符流与字节流区别与用法,较为详细的分析了java字符流与字节流的概念、功能与使用方法,具有一定参考借鉴价值,需要的朋友可以参考下
    2016-06-06
  • Netty分布式ByteBuf使用page级别的内存分配解析

    Netty分布式ByteBuf使用page级别的内存分配解析

    这篇文章主要介绍了Netty分布式ByteBuf使用page级别的内存分配解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • Java EE实现用户后台管理系统

    Java EE实现用户后台管理系统

    这篇文章主要为大家详细介绍了Java EE实现用户后台管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05

最新评论