Java泛型

基础语法

在编程中我们总是希望我们的代码能得到很好的重用,但在没有泛型之前,我们的代码都是在具体类上进行的,如果我们要写一个功能相似的类或方法使它能应用到更多的类型上,我们就只能针对不同的类型写不同的方法,这样做不仅繁琐而且扩展性非常不好(每当我们需要将这个功能应用到新的类型上时就要重新写一个类或方法)。比如我们要写一个方法实现两个数相加,在不应用泛型时,我们就只能这样写:

class Test {
    int add(int a, int b) {
        return a + b;
    }

    float add(float a, float b) {
        return a+b;
    }

    double add(double a, double b) {
        return a+b;
    }
}

为了很好地解决此类问题,Java引入了泛型编程。当要对类使用泛型编程时,只需要在类申明时在类名的后面加上”< T >”(其中T为泛型类型,可以用任意标识符,不过通常都选用T、E之类的大写字母,如果需要多个泛型类型,只需要在<>中依次申明这些类型,用”,”将其隔开就可以了),这样申明之后我们就可以在类中将T当作普通的类型使用了,只不过我们在类中我们并不知道这个T具体是什么类型就是了,比如:

class ObjectHolder<T> {
    private T t = null;
    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    @Override
    public String toString() {
        if (t !=null) {
            return t.toString();
        } else {
            return "null";
        }
    }
}

public class Test {
    public static void main(String[] args) {
        ObjectHolder<String> strHolder = new ObjectHolder<>();
        strHolder.set("Hello World");
        String s = strHolder.get();

        ObjectHolder<Integer> intHolder = new ObjectHolder<>();
        intHolder.set(3);
        int i = intHolder.get();

        //intHolder.set("Test");//compile error,intHolder只能作用在Integer上
    }
}

从上面的例子我们可以看到ObjectHolder可以应用到任何类型上,不过在ObjectHolder这个类中,我们并不知道T这个泛型类型对应的具体类型到底是什么,所以我们对t这个对象只能调用Object类中的方法(我们知道T肯定是Object或其子类,所以Object类中的方法是可以使用的)。虽然ObjectHolder可以应用到任何类型上,不过当我们申明ObjectHolder的对象将其限定在某一类型上时,那么它就只能对这种类型做相应的操作了,比如上例中的intHolder调用set(“Test”)就会报编译错误。
对方法使用泛型编程和对类使用泛型编程差不多,我们只需要在申明方法时在方法的返回类型前面申明泛型类型列表就可以了,比如:

class Test {
    static <T> void test(T t) {
        System.out.println(t.toString());
    }
}

擦除

泛型是Java在JDK1.5中引入的,在JDK1.5之前要实现ObjectHolder类似的功能就只能按照下面的方式:

class ObjectHolder {
    private Object obj;
    public Object get() {
        return obj;
    }

    public void set(Object obj) {
        this.obj = obj;
    }
}

public class Test {
    public static void main(String[] args) {
        ObjectHolder holder = new ObjectHolder();
        holder.set(3);
        int i = (int)holder.get();
        System.out.println(i);

        holder.set("hello world");
        String s = (String) holder.get();
        System.out.println(s);

        //Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
        int k = (int) holder.get();
        System.out.println(k);
    }
}

我们可以看到虽然ObjectHolder也实现了类似的功能,但同一个ObjectHolder对象可以存储不同类型的数据(因为ObjectHolder内部使用的是Object,而在Java中任何类型都是Object的子类),而且在调用get方法的时候我们得到的也只是一个Object对象,如果我们要得到具体类型的对象,我们需要强制类型转换,加入ObjectHolder内部存储对象的类型和我们强制类型转换的类型不一致在运行时就会抛出异常,比如上面的int k = (int) holder.get();为了解决上述问题,在JDK1.5中引入了泛型编程,但为了能够兼容那些使用旧版本JDK的代码(比如我们在开发一个应用时用到了第三方类库A,B,而A,B代码都是在JDk1.5之前的版本-比如1.4基础上进行开发的,那么A,B中就没有使用泛型编程-像容器等基础类库,而我们的代码很多地方都可能是在泛型编程基础上进行的,这时就存在对A,B代码兼容的问题),Java泛型使用的是运行时擦除机制。
其实Java泛型编程中的类型参数只不过是编译时的类型占位符而已,也就是在编译期间表明这是某一具体类型,但到底是什么具体类型编译器并不关心(编译器只知道它是某一限定类型,在类型参数后面使用extends Type,就表示该类型参数是Type类型或其子类型,其中Type必须是某一具体类型)。编译完成后,类型参数信息会被擦除(擦除到限定类型),所以泛型编程只不过是为我们建立起了编译期间的类型检查而已。这些我们通过反编译.class文件就可以得到,还是用ObjectHolder作为例子:

class ObjectHolder<T> {
    T t = null;

    public T get() {
        return t;
    }

    public void set(T t) {
        this.t = t;
    }
    public static void main(String[] args) {
                    ObjectHolder<Integer> intHolder = new ObjectHolder<>();
                intHolder.set(3);

                int k = intHolder.get();
        }
}

编译后我们得到ObjectHolder.class文件,我们再运行javap -verbose ObjectHolder就可以得到下面这些信息:

Classfile /Users/belows/Desktop/Java/ObjectHolder.class
  Last modified 2016-11-11; size 740 bytes
  MD5 checksum 8b1b4b5395329ded60f47771b6b1d522
  Compiled from "ObjectHolder.java"
public class ObjectHolder<T extends java.lang.Object> extends java.lang.Object
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#30        // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#31         // ObjectHolder.t:Ljava/lang/Object;
   #3 = Class              #32            // ObjectHolder
   #4 = Methodref          #3.#30         // ObjectHolder."<init>":()V
   #5 = Methodref          #8.#33         // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #6 = Methodref          #3.#34         // ObjectHolder.set:(Ljava/lang/Object;)V
   #7 = Methodref          #3.#35         // ObjectHolder.get:()Ljava/lang/Object;
   #8 = Class              #36            // java/lang/Integer
   #9 = Methodref          #8.#37         // java/lang/Integer.intValue:()I
  #10 = Class              #38            // java/lang/Object
  #11 = Utf8               t
  #12 = Utf8               Ljava/lang/Object;
  #13 = Utf8               Signature
  #14 = Utf8               TT;
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               get
  #20 = Utf8               ()Ljava/lang/Object;
  #21 = Utf8               ()TT;
  #22 = Utf8               set
  #23 = Utf8               (Ljava/lang/Object;)V
  #24 = Utf8               (TT;)V
  #25 = Utf8               main
  #26 = Utf8               ([Ljava/lang/String;)V
  #27 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;
  #28 = Utf8               SourceFile
  #29 = Utf8               ObjectHolder.java
  #30 = NameAndType        #15:#16        // "<init>":()V
  #31 = NameAndType        #11:#12        // t:Ljava/lang/Object;
  #32 = Utf8               ObjectHolder
  #33 = NameAndType        #39:#40        // valueOf:(I)Ljava/lang/Integer;
  #34 = NameAndType        #22:#23        // set:(Ljava/lang/Object;)V
  #35 = NameAndType        #19:#20        // get:()Ljava/lang/Object;
  #36 = Utf8               java/lang/Integer
  #37 = NameAndType        #41:#42        // intValue:()I
  #38 = Utf8               java/lang/Object
  #39 = Utf8               valueOf
  #40 = Utf8               (I)Ljava/lang/Integer;
  #41 = Utf8               intValue
  #42 = Utf8               ()I
{
  T t;
    descriptor: Ljava/lang/Object;
    flags:
    Signature: #14                          // TT;

  public ObjectHolder();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: aconst_null
         6: putfield      #2                  // Field t:Ljava/lang/Object;
         9: return
      LineNumberTable:
        line 1: 0
        line 2: 4

  public T get();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field t:Ljava/lang/Object;
         4: areturn
      LineNumberTable:
        line 5: 0
    Signature: #21                          // ()TT;

  public void set(T);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #2                  // Field t:Ljava/lang/Object;
         5: return
      LineNumberTable:
        line 9: 0
        line 10: 5
    Signature: #24                          // (TT;)V

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #3                  // class ObjectHolder
         3: dup
         4: invokespecial #4                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: iconst_3
        10: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        13: invokevirtual #6                  // Method set:(Ljava/lang/Object;)V
        16: aload_1
        17: invokevirtual #7                  // Method get:()Ljava/lang/Object;
        20: checkcast     #8                  // class java/lang/Integer
        23: invokevirtual #9                  // Method java/lang/Integer.intValue:()I
        26: istore_2
        27: return
      LineNumberTable:
        line 13: 0
        line 14: 8
        line 16: 16
        line 17: 27
}
Signature: #27                          // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "ObjectHolder.java"

在其中我们找到
这里写图片描述
我们可以看到变量t的描述符是Ljava/lang/Object,也就是说在编译完成后t实际上就已经是一个Object对象了,那如果这样的话我们使用泛型和不使用泛型的区别又是什么呢?在main函数中我们可以看到
这里写图片描述
在main函数中调用了get方法后,又马上将其转换为了Integer类,也就是说泛型编程在set和get边界在编译阶段为我们建立起了类型检查,使我们对ObjectHolder< Integer >类型的对象只能set Integer,并且只get后自动为我们进行了类型转换,在编译期间通过对参数列表的检查保证这样的类型转换是安全的。

类型限定

上面说到java中的泛型是将类型参数擦除到限定类型,那么什么是限定类型呢?在类型参数后面使用extends Type,其中Type就是限定类型(Type必须是某一具体类型)。比如:

class Person {
    int id;
}

class Student extends Person {
}

class Teacher extends Person {}

public class PersonHolder<T extends Person> {
    T t = null;
    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public static void main(String[] args) {
        PersonHolder<Teacher> teacherHolder = new PersonHolder<>(); 
        teacherHolder.set(new Teacher());
        Teacher teacher = teacherHolder.get();

        PersonHolder<Student> studentHolder = new PersonHolder<>();
        studentHolder.set(new Student());
        Student student = studentHolder.get();
    }
}

反编译PersonHolder.class后得到下面这些信息:

Classfile /Users/belows/Desktop/Java/PersonHolder.class
  Last modified 2016-11-11; size 681 bytes
  MD5 checksum 7be01dea0cd9ec6a253d15df60c29f8c
  Compiled from "PersonHolder.java"
public class PersonHolder<T extends Person> extends java.lang.Object
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#31        // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#32         // PersonHolder.t:LPerson;
   #3 = Class              #33            // PersonHolder
   #4 = Methodref          #3.#31         // PersonHolder."<init>":()V
   #5 = Class              #34            // Teacher
   #6 = Methodref          #5.#31         // Teacher."<init>":()V
   #7 = Methodref          #3.#35         // PersonHolder.set:(LPerson;)V
   #8 = Methodref          #3.#36         // PersonHolder.get:()LPerson;
   #9 = Class              #37            // Student
  #10 = Methodref          #9.#31         // Student."<init>":()V
  #11 = Class              #38            // java/lang/Object
  #12 = Utf8               t
  #13 = Utf8               LPerson;
  #14 = Utf8               Signature
  #15 = Utf8               TT;
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               set
  #21 = Utf8               (LPerson;)V
  #22 = Utf8               (TT;)V
  #23 = Utf8               get
  #24 = Utf8               ()LPerson;
  #25 = Utf8               ()TT;
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               <T:LPerson;>Ljava/lang/Object;
  #29 = Utf8               SourceFile
  #30 = Utf8               PersonHolder.java
  #31 = NameAndType        #16:#17        // "<init>":()V
  #32 = NameAndType        #12:#13        // t:LPerson;
  #33 = Utf8               PersonHolder
  #34 = Utf8               Teacher
  #35 = NameAndType        #20:#21        // set:(LPerson;)V
  #36 = NameAndType        #23:#24        // get:()LPerson;
  #37 = Utf8               Student
  #38 = Utf8               java/lang/Object
{
  T t;
    descriptor: LPerson;
    flags:
    Signature: #15                          // TT;

  public PersonHolder();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: aconst_null
         6: putfield      #2                  // Field t:LPerson;
         9: return
      LineNumberTable:
        line 10: 0
        line 11: 4

  public void set(T);
    descriptor: (LPerson;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #2                  // Field t:LPerson;
         5: return
      LineNumberTable:
        line 13: 0
        line 14: 5
    Signature: #22                          // (TT;)V

  public T get();
    descriptor: ()LPerson;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field t:LPerson;
         4: areturn
      LineNumberTable:
        line 17: 0
    Signature: #25                          // ()TT;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: new           #3                  // class PersonHolder
         3: dup
         4: invokespecial #4                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: new           #5                  // class Teacher
        12: dup
        13: invokespecial #6                  // Method Teacher."<init>":()V
        16: invokevirtual #7                  // Method set:(LPerson;)V
        19: aload_1
        20: invokevirtual #8                  // Method get:()LPerson;
        23: checkcast     #5                  // class Teacher
        26: astore_2
        27: new           #3                  // class PersonHolder
        30: dup
        31: invokespecial #4                  // Method "<init>":()V
        34: astore_3
        35: aload_3
        36: new           #9                  // class Student
        39: dup
        40: invokespecial #10                 // Method Student."<init>":()V
        43: invokevirtual #7                  // Method set:(LPerson;)V
        46: aload_3
        47: invokevirtual #8                  // Method get:()LPerson;
        50: checkcast     #9                  // class Student
        53: astore        4
        55: return
      LineNumberTable:
        line 21: 0
        line 22: 8
        line 23: 19
        line 25: 27
        line 26: 35
        line 27: 46
        line 28: 55
}
Signature: #28                          // <T:LPerson;>Ljava/lang/Object;
SourceFile: "PersonHolder.java"

找到
这里写图片描述

这里写图片描述

这里写图片描述
对比之前的ObjectHolde< Integer >,我们可以看到成员变量t,set的形参和get的返回值都被擦除到了Person,因为我们使用了在定义PersonHolder时使用了PersonHolder< T extends Person>,也就是类型参数T的限定类型是Person,所以在PersonHolder类编译后T就会被擦除到Person,也就如上面的结果所示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值