网站集约化建设困难,建设工程168网手机版下载,如何本地搭建自己的网站,荥阳市文章目录前序1. 泛型1.1 泛型方法1.2 泛型类1.3 泛型接口2. 泛型的基本原理3. 小结前序 
泛型是 Java 开发中常用的技术#xff0c;了解泛型的几种形式和实现泛型的基本原理#xff0c;有助于写出更优质的代码。本文总结了 Java 泛型的三种形式以及泛型实现原理。 
1. 泛型 
…
文章目录前序1. 泛型1.1 泛型方法1.2 泛型类1.3 泛型接口2. 泛型的基本原理3. 小结前序 
泛型是 Java 开发中常用的技术了解泛型的几种形式和实现泛型的基本原理有助于写出更优质的代码。本文总结了 Java 泛型的三种形式以及泛型实现原理。 
1. 泛型 
泛型的本质是对类型进行参数化在代码逻辑不关注具体的数据类型时使用。例如实现一个通用的排序算法此时关注的是算法本身而非排序的对象的类型。 
1.1 泛型方法 
如下定义了一个泛型方法 声明了一个类型变量它可以应用于参数返回值和方法内的代码逻辑。 
class GenericMethod{public T T[] sort(T[] elements){return elements;}
}1.2 泛型类 
与泛型方法类似泛型类也需要声明类型变量只不过位置放在了类名后面作用的范围包括了当前中的成员变量类型方法参数类型方法返回类型以及方法内的代码中。 
子类继承泛型类时或者实例化泛型类的对象时需要指定具体的参数类型或者声明一个参数变量。如下SubGenericClass 继承了泛型类 GenericClass其中类型变量 ID 的值为 Integer同时子类声明了另一个类型变量 E并将E 填入了父类声明的 T 中。 
class GenericClassID, T{}class SubGenericClassT extends GenericClassInteger, T{}1.3 泛型接口 
泛型接口与泛型类类似也需要在接口名后面声明类型变量作用于接口中的抽象方法返回类型和参数类型。子类在实现泛型接口时需要填入具体的数据类型或者填入子类声明的类型变量。 
interface GenericInterfaceT {T append(T seg);
}2. 泛型的基本原理 
泛型本质是将数据类型参数化它通过擦除的方式来实现。声明了泛型的 .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 ComparableUser {String name;public int compareTo(User other){return this.name.compareTo(other.name);}
}JDK 中 Comparable 接口源码内容如下 
package java.lang;
public interface ComparableT{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.javapackage java.lang;// Referenced classes of package java.lang:
//            Objectpublic 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.classUser.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.javaclass Userimplements 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 中的多态特性允许我们使用一个父类或者接口的引用指向一个子类对象。 
ComparableUser user  new User();而按照 Object 替换泛型参数原则Comparable 接口中只有 compareTo(Object) 方法假设没有桥接方法显然如下代码是不能运行的。所以 Java 编译器需要为子类泛型类的子类或泛型接口的实现类中使用了泛型的方法额外生成一个桥接方法通过这个方法来保证 Java 中的多态特性。 
ComparableUser user  new User();
Object other  new User();
user.compareTo(other);而普通类中的泛型方法在进行类型擦除时不会产生桥接方法。例如 
class Dog{T void eat(T[] food){}
}类型擦除之后变成了 
class Dog
{Dog(){}void eat(Object aobj[]){}
}3. 小结 
Java 中的泛型有 3 种形式泛型方法泛型类泛型接口。Java 通过在编译时类型擦除的方式来实现泛型。擦除时使用 Object 或者界定类型替代泛型同时在要调用具体类型方法或者成员变量的时候插入强转代码为了保证多态特性Java 编译器还会为泛型类的子类生成桥接方法。类型信息在编译阶段被擦除之后程序在运行期间无法获取类型参数所对应的具体类型。