泛型类

开发自己的泛型类

 

http://www.cnblogs.com/panjun-Donet/archive/2008/09/27/1300609.html

在定义泛型类或声明泛型类的变量时,使用尖括号来指定形式类型参数。

之所以声明泛型方法,一般是因为您想要在该方法的多个参数之间宣称一个类型约束。例如,下面代码中的 ifThenElse() 方法,根据它的第一个参数的布尔值,它将返回第二个或第三个参数:

public <T> T ifThenElse(boolean b, T first, T second) {
return b ? first : second;
}

注意,您可以调用 ifThenElse(),而不用显式地告诉编译器,您想要 T 的什么值。编译器不必显式地被告知 T 将具有什么值;它只知道这些值都必须相同。编译器允许您调用下面的代码,因为编译器可以使用类型推理来推断出,替代 T 的 String 满足所有的类型约束:

String s = ifThenElse(b, "a", "b");

类似地,您可以调用:

Integer i = ifThenElse(b, new Integer(1), new Integer(2));

但是,编译器不允许下面的代码,因为没有类型会满足所需的类型约束:

String s = ifThenElse(b, "pi", new Float(3.14));

为什么您选择使用泛型方法,而不是将类型 T 添加到类定义呢?(至少)有两种情况应该这样做:

泛型方法是静态的时,这种情况下不能使用类类型参数。

当 T 上的类型约束对于方法真正是局部的时,这意味着没有在相同类的另一个 方法签名中使用相同 类型 T 的约束。通过使得泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名。


 

形式类型参数与实际类型参数之间的关系类似于形式方法参数与实际方法参数之间的关系,只是类型参数表示类型,而不是表示值。

class ClassTest<X extends Number, Y, Z>

{

private X x;

static Y y; //编译错误,不能用在静态变量中

 public X getFirst()

 { //正确用法

return x;

 }

public void wrong()

 {

Z z = new Z(); //编译错误,不能创建对象

 }

最佳实践

在使用泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。

  • 在代码中避免泛型类和原始类型的混用。比如List<String>和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。当需要利用JDK 5之前开发的遗留代码,而不得不这么做时,也尽可能的隔离相关的代码。
  • 在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。
  • 泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List<String>[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。
  • 不要忽视编译器给出的警告信息。

1.2. 编写泛型类要注意:

  1) 在定义一个泛型类的时候,在 “<>”之间定义形式类型参数,例如:“class TestGen<K,V>”,其中“K” , “V”不代表值,而是表示类型。

  2) 实例化泛型对象的时候,一定要在类名后面指定类型参数的值(类型),一共要有两次书写。例如:

TestGen<String,String> t=new TestGen<String,String>();

  3) 泛型中<K extends Object>,extends并不代表继承,它是类型范围限制。

 

Java代码

 
 
  1. private static Class<?> getClass(Type type, int i) {     
  2.         if (type instanceof ParameterizedType) { // 处理泛型类型     
  3.             return getGenericClass((ParameterizedType) type, i);     
  4.         } else if (type instanceof TypeVariable) {     
  5.             return (Class<?>) getClass(((TypeVariable) type).getBounds()[0], 0); // 处理泛型擦拭对象     
  6.         } else {// class本身也是type,强制转型     
  7.             return (Class<?>) type;     
  8.         }     
  9.     }     
  10.     
  11.     private static Class<?> getGenericClass(ParameterizedType parameterizedType, int i) {     
  12.         Object genericClass = parameterizedType.getActualTypeArguments()[i];     
  13.         if (genericClass instanceof ParameterizedType) { // 处理多级泛型     
  14.             return (Class<?>) ((ParameterizedType) genericClass).getRawType();     
  15.         } else if (genericClass instanceof GenericArrayType) { // 处理数组泛型     
  16.             return (Class<?>) ((GenericArrayType) genericClass).getGenericComponentType();     
  17.         } else if (genericClass instanceof TypeVariable) { // 处理泛型擦拭对象     
  18.             return (Class<?>) getClass(((TypeVariable) genericClass).getBounds()[0], 0);     
  19.         } else {     
  20.             return (Class<?>) genericClass;     
  21.         }     
  22.     }    

测试代码:

Java代码

 
 
  1. interface GeneircInteface {     
  2.     
  3.     T method1(T obj);     
  4. }     
  5.     
  6. interface CommonInteface {     
  7.     
  8.     Integer method2(Integer obj);     
  9. }     
  10.     
  11. class BaseGeneircInteface implements GeneircInteface {     
  12.     
  13.     protected R result;     
  14.     
  15.     @Override    
  16.     public R method1(R obj) {     
  17.         return obj;     
  18.     }     
  19.     
  20. }     
  21.     
  22. class GenericClass extends BaseGeneircInteface<LIST> implements GeneircInteface<LIST>, CommonInteface {     
  23.     
  24.     @Override    
  25.     public List method1(List obj) {     
  26.         result = obj;     
  27.         return result;     
  28.     }     
  29.     
  30.     public Integer method2(Integer obj) {     
  31.         return obj;     
  32.     }     
  33.     
  34.     public <T,&NBSP;E&NBSP;< span>extends Throwable> T method3(T obj) throws E {     
  35.         return obj;     
  36.     }     
  37.     
  38. }    

针对class的泛型接口使用:

Java代码

 
 
  1. private static void classGeneric() {     
  2.         System.out.println("\n--------------------- classGeneric ---------------------");     
  3.         GenericClass gc = new GenericClass();     
  4.         Type[] gis = gc.getClass().getGenericInterfaces(); // 接口的泛型信息     
  5.         Type gps = gc.getClass().getGenericSuperclass(); // 父类的泛型信息     
  6.         TypeVariable<?>[] gtr = gc.getClass().getTypeParameters(); // 当前接口的参数信息     
  7.         System.out.println("============== getGenericInterfaces");     
  8.         for (Type t : gis) {     
  9.             System.out.println(t + " : " + getClass(t, 0));     
  10.         }     
  11.         System.out.println("============== getGenericSuperclass");     
  12.         System.out.println(getClass(gps, 0));     
  13.         System.out.println("============== getTypeParameters");     
  14.         for (TypeVariable t : gtr) {     
  15.             StringBuilder stb = new StringBuilder();     
  16.             for (Type tp : t.getBounds()) {     
  17.                 stb.append(tp + " : ");     
  18.             }     
  19.     
  20.             System.out.println(t + " : " + t.getName() + " : " + stb);     
  21.         }     
  22.     
  23.     }    

针对method的泛型接口使用:

Java代码

 
 
  1. private static void methodGeneric() throws Exception {     
  2.         System.out.println("\n--------------------- methodGeneric ---------------------");     
  3.         GenericClass gc = new GenericClass();     
  4.         Method method3 = gc.getClass().getDeclaredMethod("method3"new Class[] { Object.class });     
  5.     
  6.         Type[] gpt3 = method3.getGenericParameterTypes();     
  7.         Type[] get3 = method3.getGenericExceptionTypes();     
  8.         Type gt3 = method3.getGenericReturnType();     
  9.         System.out.println("============== getGenericParameterTypes");     
  10.         for (Type t : gpt3) {     
  11.             System.out.println(t + " : " + getClass(t, 0));     
  12.         }     
  13.         System.out.println("============== getGenericExceptionTypes");     
  14.         for (Type t : get3) {     
  15.             System.out.println(t + " : " + getClass(t, 0));     
  16.         }     
  17.         System.out.println("============== getType");     
  18.         System.out.println(gt3 + " : " + getClass(gt3, 0));     
  19.     }    

针对field的泛型接口使用:

Java代码

 
 
  1. private static void fieldGeneric() throws Exception {     
  2.         System.out.println("\n--------------------- fieldGeneric ---------------------");     
  3.         GenericClass gc = new GenericClass();     
  4.         Field field = gc.getClass().getSuperclass().getDeclaredField("result");     
  5.     
  6.         Type gt = field.getGenericType();     
  7.         Type ft = field.getType();     
  8.         System.out.println("============== getGenericType");     
  9.         System.out.println(gt + " : " + getClass(gt, 0));     
  10.         System.out.println("============== getType");     
  11.         System.out.println(ft + " : " + getClass(ft, 0));     
  12.     }    

输出结果:

Java代码

 
 
  1. --------------------- classGeneric ---------------------     
  2. ============== getGenericInterfaces     
  3. com.agapple.misc.GeneircInteface<JAVA.UTIL.LIST> : interface java.util.List     
  4. interface com.agapple.misc.CommonInteface : interface com.agapple.misc.CommonInteface     
  5. ============== getGenericSuperclass     
  6. interface java.util.List     
  7. ============== getTypeParameters     
  8.     
  9. --------------------- fieldGeneric ---------------------     
  10. ============== getGenericType     
  11. R : class java.lang.Object     
  12. ============== getType     
  13. class java.lang.Object : class java.lang.Object     
  14.     
  15. --------------------- methodGeneric ---------------------     
  16. ============== getGenericParameterTypes     
  17. T : class java.lang.Object     
  18. ============== getGenericExceptionTypes     
  19. E : class java.lang.Throwable     
  20. ============== getType     
  21. T : class java.lang.Object   

结果说明:

因为泛型的擦拭,对应的GeneircInteface和BaseGeneircInteface,在源码信息已被擦除对应的类型,进行了upper转型,所以取到的是Object。可以使用extends

GenericClass在类定义时,声明了继承父接口的泛型为List,所以再通过接口和父类获取泛型信息时,是能正确的获取。通过javap -v可以获取对应的class信息

Java代码

 
 
  1. const #46 = Asciz   Lcom/agapple/misc/BaseGeneircInteface<LJAVA lang="" util List;>;Lcom/agapple/misc/GeneircInteface<LJAVA lang="" util List;>;Lcom/agapple/misc/CommonInteface;;     

而在GenericClass中定义的方法method3,在class信息是一个被向上转型后擦拭的信息。所以获取method3的相关泛型信息是没有的。

Java代码

 
 
  1. method3;     
  2. const #36 = Asciz   (Ljava/lang/Object;)Ljava/lang/Object;;     
  3. const #37 = Asciz   Exceptions;     
  4. const #38 = class   #39;    //  java/lang/Throwable     
  5. const #39 = Asciz   java/lang/Throwable;     
  6. const #40 = Asciz   (TT;)TT;^TE;;     
  7. const #41 = Asciz   TT;;    

思考问题:

List list = new ArrayList(); 是否有获取对应的String泛型信息? 不能,临时变量不能保存泛型信息到具体class对象中,List和List对应的class实体是同一个。

Java代码

 
 
  1. GeneircInteface gi = new GeneircInteface() {     
  2.     
  3.             @Override    
  4.             public Integer method1(Integer obj) {     
  5.                 return 1;     
  6.             }     
  7.     
  8.         };    

通过匿名类的方式,是否可以获取Integer的泛型信息? 能,匿名类也会在进行class compiler保存泛型信息。

假如本文例子中的method3,是放在父类中BaseGeneircInteface中进行申明,GenericClass中指定R为List,是否可以获取到对应的泛型信息? 不能,理由和问题1类似。

备注

具体泛型擦拭和信息保存,引用了撒迦的一段回复,解释的挺详尽了。

RednaxelaFX 写道

Java泛型有这么一种规律:

位于声明一侧的,源码里写了什么到运行时就能看到什么;

位于使用一侧的,源码里写什么到运行时都没了。

什么意思呢?“声明一侧”包括泛型类型(泛型类与泛型接口)声明、带有泛型参数的方法和域的声明。注意局部变量的声明不算在内,那个属于“使用”一侧。

Java代码

 
 
  1. import java.util.List;      
  2. import java.util.Map;      
  3.     
  4. public class GenericClass { // 1      
  5. private List list; // 2      
  6. private Map map; // 3      
  7.     
  8. public  U genericMethod(Map m) { // 4      
  9. return null;      
  10. }      
  11. }     

上面代码里,带有注释的行里的泛型信息在运行时都还能获取到,原则是源码里写了什么运行时就能得到什么。针对1的GenericClass,运行时通过Class.getTypeParameters()方法得到的数组可以获取那个“T”;同理,2的T、3的java.lang.String与T、4的T与U都可以获得。

这是因为从Java 5开始class文件的格式有了调整,规定这些泛型信息要写到class文件中。以上面的map为例,通过javap来看它的元数据可以看到记录了这样的信息:

Javap代码

 
 
  1. private java.util.Map map;      
  2. Signature: Ljava/util/Map;      
  3. Signature: length = 0x2      
  4. 00 0A     

乍一看,private java.util.Map map;不正好显示了它的泛型类型被擦除了么?

但仔细看会发现有两个Signature,下面的一个有两字节的数据,0x0A。到常量池找到0x0A对应的项,是:

Javap代码

 
 
  1. const #10 = Asciz Ljava/util/Map;;    

也就是内容为“Ljava/util/Map;”的一个字符串。

根据Java 5开始的新class文件格式规范,方法与域的描述符增添了对泛型信息的记录,用一对尖括号包围泛型参数,其中普通的引用类型用“La/b/c/D;”的格式记录,未绑定值的泛型变量用“Txxx;”的格式记录,其中xxx就是源码中声明的泛型变量名。类型声明的泛型信息也以类似下面的方式记了下来:

Javap代码

 
 
  1. public class GenericClass extends java.lang.Object      
  2. Signature: length = 0x2      
  3. 00 12      
  4. // ...      
  5. const #18 = Asciz Ljava/lang/Object;;    

详细信息请参考官方文档:http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf

相比之下,“使用一侧”的泛型信息则完全没有被保留下来,在Java源码编译到class文件后就确实丢失了。也就是说,在方法体内的泛型局部变量、泛型方法调用之类的泛型信息编译后都消失了。

Java代码

 
 
  1. import java.util.ArrayList;      
  2. import java.util.List;      
  3.     
  4. public class TestClass {      
  5. public static void main(String[] args) {      
  6. List list = null// 1      
  7. list = new ArrayList(); // 2      
  8. for (int i = 0; i < 10; i++) ;      
  9. }      
  10. }   

上面代码中,1留下的痕迹是:main()方法的StackMapTable属性里可以看到:

Java代码

 
 
  1. StackMapTable: number_of_entries = 2      
  2. frame_type = 253 /* append */      
  3. offset_delta = 12      
  4. locals = [ class java/util/List, int ]      
  5. frame_type = 250 /* chop */      
  6. offset_delta = 11     

但这里是没有留下泛型信息的。这段代码只所以写了个空的for循环就是为了迫使javac生成那个StackMapTable,让1多留个影。

如果main()里用到了list的方法,那么那些方法调用点上也会留下1的痕迹,例如如果调用list.add("");,则会留下“java/util/List.add:(Ljava/lang/Object;)Z”这种记录。

2留下的是“java/util/ArrayList."":()V”,同样也丢失了泛型信息。

由上述讨论可知,想对带有未绑定的泛型变量的泛型类型获取其实际类型是不现实的,因为class文件里根本没记录实际类型的信息。觉得这句话太拗口的话用例子来理解:要想对java.util.List获取E的实际类型是不现实的,因为List.class文件里只记录了E,却没记录使用List时E的实际类型。

想对局部变量等“使用一侧”的已绑定的泛型类型获取其实际类型也不现实,同样是因为class文件中根本没记录这个信息。例子直接看上面讲“使用一侧”的就可以了。

知道了什么信息有记录,什么信息没有记录之后,也就可以省点力气不去纠结“拿不到T的实际类型”、“建不出T类型的数组”之类的问题了orz

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值