Java笔记(一)——泛型

来源:互联网 发布:天马运动网络分销平台 编辑:程序博客网 时间:2024/04/26 10:52

泛型类

泛型类是具有一个或多个类型变量的类。从字面上可以看出泛型类是具有类型参数的类,使得同一个类型或方法可以被很多不同的类型所复用。下面是使用Mybatis作为ORM框架时自定义的一个BaseDAO类。实体类类型非常多不可能为每一个实体类单独写数据库相关操作,为了复用给BaseDAO加上了参数类型T,使其可以适用所有实体类型。

import java.util.ArrayList;import java.util.List;import java.util.Map;import org.mybatis.spring.SqlSessionTemplate;public class BaseDAO<T> {private SqlSessionTemplate sqlSession;       public SqlSessionTemplate getSqlSession() {              return sqlSession;       }       public void setSqlSession(SqlSessionTemplate sqlSession) {              this.sqlSession = sqlSession;       }       @SuppressWarnings("unchecked")       public T selectOne(String statement,Object parameter) {              T obj = null;              try{                     obj = (T)sqlSession.selectOne(statement, parameter);              } catch (Exception e) {                     e.printStackTrace();              }              return obj;       }       public Object selectOneObject(String statement,Object parameter) {              Object obj = null;              try {                     obj = (Object)sqlSession.selectOne(statement, parameter);              } catch (Exception e) {                     e.printStackTrace();              }              return obj;       }       public Map<String, T> selectStringKeyMap(String statement, Object parameter, String mapKey) {              Map<String,T> map = sqlSession.selectMap(statement, parameter, mapKey);              return map;       }       public Map<Integer, T> selectIntKeyMap(String statement, Object parameter, String mapKey) {              Map<Integer,T> map = sqlSession.selectMap(statement, parameter, mapKey);              return map;       }       @SuppressWarnings("unchecked")       public List<T> selectList(String statement, Object parameter) {              List<T> result = null;              try {                     result = (List<T>)sqlSession.selectList(statement, parameter);              } catch (Exception e) {                     e.printStackTrace();              }              return result;       }       public List<Object> selectObjectList(String statement, Object parameter) {              List<Object> result = null;              result = (List<Object>)sqlSession.selectList(statement, parameter);              return result;       }       public List<String> selectStringList(String statement, Object parameter) {              List<String> result = new ArrayList<String>();              List<Object> objs = null;              objs = sqlSession.selectList(statement,parameter);              for(Object obj : objs) {                     result.add((String)obj);              }              return result;       }       public int selectInt(String statement,Object parameter) {              Object obj = sqlSession.selectOne(statement, parameter);              return obj == null ? 0 : (Integer)obj;       }}

泛型方法

泛型方法是具有类型参数的方法,可以在普通类中定义泛型方法。定义泛型方法时类型变量放在修饰符后返回变量前。

public <T extends Comparable<T>> T min(T one, T two) {              if(one.compareTo(two) > 0) {                     return two;              }              return one;}
在调用泛型方法时一般需要在方法名前用尖括号指明具体类型,但是一般编译器能够通过参数信息推断出具体类型,因此可以省略。
test.<String>min("one", "two");test.min("one", "two");
编译器可以通过参数"one","two"推断出具体类型为String。

类型变量
类型变量可以通过extends来添加边界约束。如泛型方法示例中的T extends Comparable就限定了该方法的参数类型必须是实现了Comparable接口的类型。因为如果没有实现Comparable接口那么就不能保证类型中含有compareTo方法。在这里Comparable被称为限定类型(Bound Type)。一个类型变量可以由多个限定变量约束,如T extends Comparable & Serializable。注意如果限定类型中有类那么必须写在第一个位置,在接口前面。如Test是一个普通类。

@SuppressWarnings("unchecked")       public <T extends Test & Comparable<T>> T min(T one, T two) {  //正确              if(one.compareTo(two) > 0) {                     return two;              }              return one;       }@SuppressWarnings("unchecked")       public <T extends Comparable<T> & Test> T min(T one, T two) {//错误:The type Test is not an interface; it cannot be specified as a bounded parameter              if(one.compareTo(two) > 0) {                     return two;              }              return one;       }
通配符类型
通配符类型形如

? extends Employee? super Worker
先看以下类图


使用通配符类型可以添加extends和super限定,相当于是增加了一个类型层次,比起固定的参数类型GenericTypeExample<T>更加具有灵活性。GenericTypeExample<? extends Employee> gte; 可以赋值为GenericTypeExample<Manager>的对象或者GenericTypeExample<Worker>的对象。

类型擦除

在虚拟机中所有类型都是普通类型,不存在泛型类型的对象。Java是通过类型擦除机制将泛型类型转变为普通类型。擦除(Erased)是将泛型类型转变为普通类型,首先将原始类型(Raw Type)后的类型参数去掉只保留原始类型。然后使用限定类型(Bound Type)替换所有类型参数,如果没有限定类型则用Object替换。以下代码定义了一个泛型类型GenericTypeExample<T>,对应的原始类型为GenericTypeExample

public class GenericTypeExample<T> {       private T field1;       private T field2;       public GenericTypeExample() {       }       public GenericTypeExample(T field1, T field2) {              this.field1 = field1;              this.field2 = field2;       }       public T getField1() {              return field1;       }       public void setField1(T field1) {              this.field1 = field1;       }       public T getField2() {              return field2;       }       public void setField2(T field2) {              this.field2 = field2;       }}

将 GenericTypeExample进行反编译结果如下:

D:\>javac GenericTypeExample.javaD:\>javap -verbose GenericTypeExampleCompiled from "GenericTypeExample.java"public class GenericTypeExample extends java.lang.Object  Signature: length = 0x2   00 19  SourceFile: "GenericTypeExample.java"  minor version: 0  major version: 50  Constant pool:const #1 = Method       #5.#28; //  java/lang/Object."<init>":()Vconst #2 = Field        #4.#29; //  GenericTypeExample.field1:Ljava/lang/Object;const #3 = Field        #4.#30; //  GenericTypeExample.field2:Ljava/lang/Object;const #4 = class        #31;    //  GenericTypeExampleconst #5 = class        #32;    //  java/lang/Objectconst #6 = Asciz        field1;const #7 = Asciz        Ljava/lang/Object;;const #8 = Asciz        Signature;const #9 = Asciz        TT;;const #10 = Asciz       field2;const #11 = Asciz       <init>;const #12 = Asciz       ()V;const #13 = Asciz       Code;const #14 = Asciz       LineNumberTable;const #15 = Asciz       (Ljava/lang/Object;Ljava/lang/Object;)V;const #16 = Asciz       (TT;TT;)V;const #17 = Asciz       getField1;const #18 = Asciz       ()Ljava/lang/Object;;const #19 = Asciz       ()TT;;const #20 = Asciz       setField1;const #21 = Asciz       (Ljava/lang/Object;)V;const #22 = Asciz       (TT;)V;const #23 = Asciz       getField2;const #24 = Asciz       setField2;const #25 = Asciz       <T:Ljava/lang/Object;>Ljava/lang/Object;;const #26 = Asciz       SourceFile;const #27 = Asciz       GenericTypeExample.java;const #28 = NameAndType #11:#12;//  "<init>":()Vconst #29 = NameAndType #6:#7;//  field1:Ljava/lang/Object;const #30 = NameAndType #10:#7;//  field2:Ljava/lang/Object;const #31 = Asciz       GenericTypeExample;const #32 = Asciz       java/lang/Object;{public GenericTypeExample();  Code:   Stack=1, Locals=1, Args_size=1   0:   aload_0   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V   4:   return  LineNumberTable:   line 6: 0   line 8: 4public GenericTypeExample(java.lang.Object, java.lang.Object);  Code:   Stack=2, Locals=3, Args_size=3   0:   aload_0   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V   4:   aload_0   5:   aload_1   6:   putfield        #2; //Field field1:Ljava/lang/Object;   9:   aload_0   10:  aload_2   11:  putfield        #3; //Field field2:Ljava/lang/Object;   14:  return  LineNumberTable:   line 10: 0   line 11: 4   line 12: 9   line 13: 14  Signature: length = 0x2   00 10public java.lang.Object getField1();  Code:   Stack=1, Locals=1, Args_size=1   0:   aload_0   1:   getfield        #2; //Field field1:Ljava/lang/Object;   4:   areturn  LineNumberTable:   line 16: 0  Signature: length = 0x2   00 13public void setField1(java.lang.Object);  Code:   Stack=2, Locals=2, Args_size=2   0:   aload_0   1:   aload_1   2:   putfield        #2; //Field field1:Ljava/lang/Object;   5:   return  LineNumberTable:   line 20: 0   line 21: 5  Signature: length = 0x2   00 16public java.lang.Object getField2();  Code:   Stack=1, Locals=1, Args_size=1   0:   aload_0   1:   getfield        #3; //Field field2:Ljava/lang/Object;   4:   areturn  LineNumberTable:   line 24: 0  Signature: length = 0x2   00 13public void setField2(java.lang.Object);  Code:   Stack=2, Locals=2, Args_size=2   0:   aload_0   1:   aload_1   2:   putfield        #3; //Field field2:Ljava/lang/Object;   5:   return  LineNumberTable:   line 28: 0   line 29: 5  Signature: length = 0x2   00 16}
从反编译的结果可以看出,GenericTypeExample编译后的类型为public class GenericTypeExample extends java.lang.Object。其中所有出现T的地方均被替换为Object(如果T有限定类型则会被替换为第一个限定类型)。

public class Test {       public static void main(String[] args) {              GenericTypeExample<Test> gte = new GenericTypeExample(new Test(), new Test());              Test t = gte.getField1();  //取第一个属性              gte.setField2(new Test()); //为第二个属性赋值       }}

以上代码Test类中只有一个main方法,调用GenericTypeExample的构造函数new了一个GenericTypeExample的对象,并取出field1赋值给t。从上面的反编译结果来看不存在GenericTypeExample<Test>的类型,getField1()方法的返回类型也为Object。那为什么Test t = gte.getField1();并没有报错呢?实际上编译器在这里添加了一个强制类型转换,将Object强制转换为Test。gte.setField2(new Test()); 为第2个属性赋值,类型擦除后setField2()方法的参数类型应为Object,这并没有问题。
我们再看下Test的反编译结果

D:\>javap -verbose TestCompiled from "Test.java"public class Test extends java.lang.Object  SourceFile: "Test.java"  minor version: 0  major version: 50  Constant pool:const #1 = Method       #8.#17; //  java/lang/Object."<init>":()Vconst #2 = class        #18;    //  GenericTypeExampleconst #3 = class        #19;    //  Testconst #4 = Method       #3.#17; //  Test."<init>":()Vconst #5 = Method       #2.#20; //  GenericTypeExample."<init>":(Ljava/lang/Obje ct;Ljava/lang/Object;)Vconst #6 = Method       #2.#21; //  GenericTypeExample.getField1:()Ljava/lang/Ob ject;const #7 = Method       #2.#22; //  GenericTypeExample.setField2:(Ljava/lang/Obj ect;)Vconst #8 = class        #23;    //  java/lang/Objectconst #9 = Asciz        <init>;const #10 = Asciz       ()V;const #11 = Asciz       Code;const #12 = Asciz       LineNumberTable;const #13 = Asciz       main;const #14 = Asciz       ([Ljava/lang/String;)V;const #15 = Asciz       SourceFile;const #16 = Asciz       Test.java;const #17 = NameAndType #9:#10;//  "<init>":()Vconst #18 = Asciz       GenericTypeExample;const #19 = Asciz       Test;const #20 = NameAndType #9:#24;//  "<init>":(Ljava/lang/Object;Ljava/lang/Object ;)Vconst #21 = NameAndType #25:#26;//  getField1:()Ljava/lang/Object;const #22 = NameAndType #27:#28;//  setField2:(Ljava/lang/Object;)Vconst #23 = Asciz       java/lang/Object;const #24 = Asciz       (Ljava/lang/Object;Ljava/lang/Object;)V;const #25 = Asciz       getField1;const #26 = Asciz       ()Ljava/lang/Object;;const #27 = Asciz       setField2;const #28 = Asciz       (Ljava/lang/Object;)V;{public Test();  Code:   Stack=1, Locals=1, Args_size=1   0:   aload_0   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V   4:   return  LineNumberTable:   line 2: 0public static void main(java.lang.String[]);  Code:   Stack=5, Locals=3, Args_size=1   0:   new     #2; //class GenericTypeExample   3:   dup   4:   new     #3; //class Test   7:   dup   8:   invokespecial   #4; //Method "<init>":()V   11:  new     #3; //class Test   14:  dup   15:  invokespecial   #4; //Method "<init>":()V   18:  invokespecial   #5; //Method GenericTypeExample."<init>":(Ljava/lang/Obj ect;Ljava/lang/Object;)V   21:  astore_1   22:  aload_1   23:  invokevirtual   #6; //Method GenericTypeExample.getField1:()Ljava/lang/O bject;   26:  checkcast       #3; //class Test   29:  astore_2   30:  aload_1   31:  new     #3; //class Test   34:  dup   35:  invokespecial   #4; //Method "<init>":()V   38:  invokevirtual   #7; //Method GenericTypeExample.setField2:(Ljava/lang/Ob ject;)V   41:  return  LineNumberTable:   line 5: 0   line 6: 22   line 7: 30   line 8: 41}

如果在声明GenericTypeExample时不添加类型参数,如图中所示,编译器不会自动添加Test的强制类型转换,报错"Type mismatch: cannot convert from Object  to Test”。


从以上内容可以看出:
  • 在虚拟机内不存在泛型类型
  • 擦除后编译器会在存取泛型域的方法基础上加上强制类型转换。从这里可以看出虽然编译器会擦除泛型类型但是仍然具有泛型类型的记忆
桥方法
看如下代码,SubGenericTypeExample类是extends GenericTypeExample<Test>的一个子类型。
public class GenericTypeExample<T> {       private T field1;       private T field2;       public GenericTypeExample() {       }       public GenericTypeExample(T field1, T field2) {              this.field1 = field1;              this.field2 = field2;       }       public void setField1(T field1) {              System.out.println("super class method");              this.field1 = field1;       }}public class SubGenericTypeExample extends GenericTypeExample<Test> {       @Override       public void setField1(Test field1) {              System.out.println("sub class method");       }}

SubGenericTypeExample中覆写了父类中的setField1(T field1);从上面的擦除部分可以知道在类GenericTypeExample中方法会变为public void setField1(Object field1);由于SubGenericTypeExample是子类,所以也继承了该方法。但是SubGenericTypeExample本身继承的类型为GenericTypeExample<Test>并且覆写了setField1方法为public void setField1(Test field1);该方法与public void setField1(Object field1);相比参数类型不同,完全是两个方法,由此就产生了冲突。
public static void main(String[] args) {              GenericTypeExample<Test> gte = null;              SubGenericTypeExample sgte = new SubGenericTypeExample();              gte = sgte;              gte.setField1(new Test());}
如果将子类实体对象赋给父类GenericTypeExample变量,并且调用setField1方法,那肯定是执行参数为Object的方法,那就与预期的本身应该调用SubGenericTypeExample的setField1(Test field1)冲突。
实际上编译器会为SubGenericTypeExample类生成一个桥方法,桥方法调用覆写后的方法来解决冲突。SubGenericTypeExample类的反编译结果如下:
D:\>javap -verbose SubGenericTypeExampleCompiled from "SubGenericTypeExample.java"public class SubGenericTypeExample extends GenericTypeExample  Signature: length = 0x2   00 11  SourceFile: "SubGenericTypeExample.java"  minor version: 0  major version: 50  Constant pool:const #1 = Method       #8.#20; //  GenericTypeExample."<init>":()Vconst #2 = Field        #21.#22;        //  java/lang/System.out:Ljava/io/PrintStream;const #3 = String       #23;    //  sub class methodconst #4 = Method       #24.#25;        //  java/io/PrintStream.println:(Ljava/lang/String;)Vconst #5 = class        #26;    //  Testconst #6 = Method       #7.#27; //  SubGenericTypeExample.setField1:(LTest;)Vconst #7 = class        #28;    //  SubGenericTypeExampleconst #8 = class        #29;    //  GenericTypeExampleconst #9 = Asciz        <init>;const #10 = Asciz       ()V;const #11 = Asciz       Code;const #12 = Asciz       LineNumberTable;const #13 = Asciz       setField1;const #14 = Asciz       (LTest;)V;const #15 = Asciz       (Ljava/lang/Object;)V;const #16 = Asciz       Signature;const #17 = Asciz       LGenericTypeExample<LTest;>;;const #18 = Asciz       SourceFile;const #19 = Asciz       SubGenericTypeExample.java;const #20 = NameAndType #9:#10;//  "<init>":()Vconst #21 = class       #30;    //  java/lang/Systemconst #22 = NameAndType #31:#32;//  out:Ljava/io/PrintStream;const #23 = Asciz       sub class method;const #24 = class       #33;    //  java/io/PrintStreamconst #25 = NameAndType #34:#35;//  println:(Ljava/lang/String;)Vconst #26 = Asciz       Test;const #27 = NameAndType #13:#14;//  setField1:(LTest;)Vconst #28 = Asciz       SubGenericTypeExample;const #29 = Asciz       GenericTypeExample;const #30 = Asciz       java/lang/System;const #31 = Asciz       out;const #32 = Asciz       Ljava/io/PrintStream;;const #33 = Asciz       java/io/PrintStream;const #34 = Asciz       println;const #35 = Asciz       (Ljava/lang/String;)V;{public SubGenericTypeExample();  Code:   Stack=1, Locals=1, Args_size=1   0:   aload_0   1:   invokespecial   #1; //Method GenericTypeExample."<init>":()V   4:   return  LineNumberTable:   line 2: 0public void setField1(Test);  Code:   Stack=2, Locals=2, Args_size=2   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;   3:   ldc     #3; //String sub class method   5:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V   8:   return  LineNumberTable:   line 6: 0   line 7: 8public void setField1(java.lang.Object);  Code:   Stack=2, Locals=2, Args_size=2   0:   aload_0   1:   aload_1   2:   checkcast       #5; //class Test   5:   invokevirtual   #6; //Method setField1:(LTest;)V   8:   return  LineNumberTable:   line 2: 0}

通过上面的例子可以看出,由擦除引起的冲突编译器会通过生成桥方法来解决。让由类型擦除后通过父类得到的类型为Object(或限定类型)的方法调用子类覆写的方法来实现一致性,化解冲突。