java基础加强--泛型(Generic)的应用
2011-02-20 21:19
495 查看
泛型是JAVA 1.5的新特性,引入泛型主要是为了解决数据的类型安全问题。在类的声明时
通过使用一个标识符号(如<T>、<K,V>、<?>等)添加在类名,属性,方法(方法名,参数
以及返回值)上。这样用户在声明或实例化时只要指定好需要的具体类型即可。1.5后集合
类和反射都增加了泛型的支持,先从集合类的一个例子来直观了解泛型的作用和使用方法。
例:
通过上面的例子可以看出,使用泛型后,编译器会通过声明的泛型来限定集合中的元素类型,
用户如果加入错误的类型,就会报错。通过这样的方式就可以保证集合中的类型一致。当用
户在集合中取出一个元素时,也不需要进行强制类型转换了,因为编译器已经知道了集合中
的元素只能是某个用户指定的类型。
泛型的内部作用:
泛型其实是提供给JAVAC编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序
中的非法输入,编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受
影响,对于参数化的泛型类型,getclass()方法的返回值类型和原始类型一样。由于编译器
编译器产生的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中
加入其它类型的数据。例如,用反射得到集合,再调用其ADD方法。
例2:
输出结果为abc
但是写成这样就报错了!
例3:
将会报出:Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String at
cn.hxq.generic.ReflyectGeneric.main(ReflyectGeneric.java:16)
我的猜想:
System.out.println(list1.get(0));
在list1.get(0)这里,由于编译器认为返回的对象类型应该为String,但我因为
使用了反射在集合中添加了Integer类型的对象,list1.get(0)给出的其实
是Integer。于是我再想:System.out.println不是会自动调用toString()方法吗?
后来发现,我的声明是List<String> list1
里面装的肯定是String,get()方法返回的肯定是String,那编译器肯定不能对一个
String类型的对象去调用toString()方法。于是编译器就相当于去做了这么一件事:
Integer i = 1;
String str = i;
那样就肯定报错了,改成以下两句就可以了。
Object obj = list1.get(0);
System.out.println(obj);
我想原来错误就是由于System.out.println语句没法调用toString方法的缘故。
查阅JDK文档我却赫然发现:String类竟然也有toString()方法!那么前面的推断是
错的吗?(String 的toString()方法的解释很搞笑!)
我于是再试了一下这样的方式:
这时我使用了list2,我指定的集合类型是ArrayList<Integer>,通过反射加入
了"abc"这个String对象,从例2来看,直接System.out.println(list2.get(0));
这样可以很好的打印出abc。但这次我显示地加入了toString()方法。让人吃惊的
是,报错了:Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String at
cn.hxq.generic.ReflyectGeneric.main(ReflyectGeneric.java:16)。
还是和以前一样的异常类型。这样可以证明我前面的推论其实是错的。
想了好久也没想出来,后来请教了群里的同学,同学认为:问题不是出在
toString()是否被调用的前提下,而是在get()这个方法中。
同学的观点:
看看java源代码关于get()方法的定义:
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
这是一个返回值是泛型的方法,编译器在编译的list1.get(0)的时候,因为前面
我声明了List<String> list1,由于去泛型化的原因那么通过list1调用的get()
方法自然就被编译成:
public String get (int index){
return (String) elmentData[index];
}
在运行时,就需要返回一个String类型的值.
而且在return 的时候,会将我加入的Integer强制转换成String类型,于是就报了
java.lang.ClassCastException的错。其实就是类型转换的错误。
新衍生的问题:
那为什么System.out.println(list2.get(0))是正确的,
System.out.println(list2.get(0).toString())却又报错了呢?
如果按照上面的推论:
我声明了List<String> list2,我使用list2调用的get()方法应该会成为这样的形式:
public Integer (int index){
return (Integer) elmentData[index];
}
那么此时将String类型的元素强制转换成Integer的时候却又不报错了呢?再者,为什么一旦显式调用了toString(),却马上报错了?
这些问题实在思考不出来,先丢在一边··。
昨晚没有思考出来,今天早上突然有了灵感。
其实问题是出在get方法的return之后,get()方法本身只是通过return将集合中的引用传递出来
,println()方法接到引用之后,会对接到的引用根据类型去调用编译器在class文件中说明的
toString()方法,因为这个方法是被不同的子类覆写过的。
问题1的解答:
list1.get(0)编译器编译的时候认为它返回的是String值,于是在Class文件中标明了一旦
需要调用toString(),则会使用String类的toString()(实际上如果真是String类,JVM将不会调用)
运行时传递来了一个Integer类的对象,那么就需要调用toString()方法了。
此时JVM就去用一个Integer的对象调用了String类的toString()方法,因此报出类型转换的错误。
Object obj = list.get(0);
这一句很好地说明了get()方法本身内部在进行引用传递时并没有出错。
错在于接受引用的方法或者引用变量。
System.out.println(obj);
此时会调用obj的toString方法肯定是没错的,于是正确打印了。
问题2的解答:
System.out.println(list2.get(0));这样为什么正确呢?
因为println()实际上接到的引用是String类的,JVM没必要再调用编译器在class文件中声明的
Integer类的toString()方法,所以直接就能打印了。
System.out.println(list2.get(0).toString());
这样的显式调用为什么错误?这也是编译器在编译成class文件时造成的,因为
我显式调用了toString(),因为编译器认为我传递出来的是个Integer,所以在class文件
中声明的是Integer类的toString()方法。所以我显示调用时,实际上是用String()类的
对象去调用了Integer类的toString()方法。这样就出错了。
总结如下:
一、
list1Method.invoke(list1, 1);
System.out.println(list1.get(0));
错误:1)、get()方法运行时返回了一个Integer对象,println()方法无法打印。
2)、JVM对Integer对象的处理:去调用toString()方法。
3)、class文件中的信息让它去调用了一个String类的toString()方法。
4)、使用Integer对象调用了String类的toString()方法,出错了。
二、
list1Method.invoke(list2, "abc");
System.out.println(list2.get(0));
正确:1)、get()方法运行时返回了一个String对象,println()方法
接到String类对象。
2)、JVM对String对象的处理:不调用toString()方法。正确了。
三、
list1Method.invoke(list2, "abc");
System.out.println(list2.get(0).toString());
错误:1)、get()方法运行时返回了一个String对象,println()方法
接到String类对象。
2)、此时toString()方法被显式调用,由于class文件得到的类型信息,调用
的是Integer类的toString()方法。
3)、使用String对象去调用Integer类的toString()方法,出错了。
结论是:
使用一个类的对象 去调用了 另一个非父类的toString()方法,导致出错。
如果使用String类,默认是不会调用toString()方法,如果显式调用,会重复上面的
错误。
泛型的应用很广泛,应该牢牢掌握,但如果要深入了解泛型的实现,我想可不是一朝一
夕的事··。
通过使用一个标识符号(如<T>、<K,V>、<?>等)添加在类名,属性,方法(方法名,参数
以及返回值)上。这样用户在声明或实例化时只要指定好需要的具体类型即可。1.5后集合
类和反射都增加了泛型的支持,先从集合类的一个例子来直观了解泛型的作用和使用方法。
例:
package cn.hxq.generic; import java.lang.reflect.*; import java.util.ArrayList; public class GenericTest { @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception{ //1.5以前的集合使用:集合中可以加入多种类型,导致使用时类型出错 ArrayList collection1 = new ArrayList(); collection1.add(1); collection1.add(1L); collection1.add("abc"); //如果这样拿出来用就出错了: //int i = (Integer)collection1.get(1); //使用泛型改写上面的集合 ArrayList<String> collection2 = new ArrayList<String>(); //此时下面两个内容将不能加入到集合中,保证了集合只能存储String类型的对象 //collection2.add(1); //collection1.add(1L); collection2.add("abc"); String element = collection2.get(0); //反射中也可以使用泛型: Constructor<StringBuffer> construtor = StringBuffer.class.getConstructor(String.class); StringBuffer sbr = construtor.newInstance(element); Method method = sbr.getClass().getMethod("append", String.class); System.out.println(method.invoke(sbr, element)); } }
通过上面的例子可以看出,使用泛型后,编译器会通过声明的泛型来限定集合中的元素类型,
用户如果加入错误的类型,就会报错。通过这样的方式就可以保证集合中的类型一致。当用
户在集合中取出一个元素时,也不需要进行强制类型转换了,因为编译器已经知道了集合中
的元素只能是某个用户指定的类型。
泛型的内部作用:
泛型其实是提供给JAVAC编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序
中的非法输入,编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受
影响,对于参数化的泛型类型,getclass()方法的返回值类型和原始类型一样。由于编译器
编译器产生的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中
加入其它类型的数据。例如,用反射得到集合,再调用其ADD方法。
例2:
import java.util.*; import java.lang.reflect.*; public class ReflyectGeneric { public static void main(String[] args) throws Exception { List<String> list1 = new ArrayList<String>(); List<Integer> list2 = new ArrayList<Integer>(); //使用反射得到ArrayList集合的字节码,看是否是同一份 System.out.println(list1.getClass() == list2.getClass()); //通过反射调用集合的add方法来往集合中加入不同类型的元素 Method list1Method = list1.getClass().getMethod("add",Object.class); list1Method.invoke(list2, "abc"); System.out.println(list2.get(0)); } }
输出结果为abc
但是写成这样就报错了!
例3:
package cn.hxq.generic; import java.util.*; import java.lang.reflect.*; public class ReflyectGeneric { public static void main(String[] args) throws Exception { List<String> list1 = new ArrayList<String>(); List<Integer> list2 = new ArrayList<Integer>(); //使用反射得到ArrayList集合的字节码,看是否是同一份 System.out.println(list1.getClass() == list2.getClass()); //通过反射调用集合的add方法来往集合中加入不同类型的元素 Method list1Method = list1.getClass().getMethod("add",Object.class); list1Method.invoke(list1, 1); System.out.println(list1.get(0)); } }
将会报出:Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String at
cn.hxq.generic.ReflyectGeneric.main(ReflyectGeneric.java:16)
我的猜想:
System.out.println(list1.get(0));
在list1.get(0)这里,由于编译器认为返回的对象类型应该为String,但我因为
使用了反射在集合中添加了Integer类型的对象,list1.get(0)给出的其实
是Integer。于是我再想:System.out.println不是会自动调用toString()方法吗?
后来发现,我的声明是List<String> list1
里面装的肯定是String,get()方法返回的肯定是String,那编译器肯定不能对一个
String类型的对象去调用toString()方法。于是编译器就相当于去做了这么一件事:
Integer i = 1;
String str = i;
那样就肯定报错了,改成以下两句就可以了。
Object obj = list1.get(0);
System.out.println(obj);
我想原来错误就是由于System.out.println语句没法调用toString方法的缘故。
查阅JDK文档我却赫然发现:String类竟然也有toString()方法!那么前面的推断是
错的吗?(String 的toString()方法的解释很搞笑!)
我于是再试了一下这样的方式:
package cn.hxq.generic; import java.util.*; import java.lang.reflect.*; public class ReflyectGeneric { public static void main(String[] args) throws Exception { List<String> list1 = new ArrayList<String>(); List<Integer> list2 = new ArrayList<Integer>(); //使用反射得到ArrayList集合的字节码,看是否是同一份 System.out.println(list1.getClass() == list2.getClass()); //通过反射调用集合的add方法来往集合中加入不同类型的元素 Method list1Method = list1.getClass().getMethod("add",Object.class); list1Method.invoke(list2, "abc"); System.out.println(list2.get(0).toString()); } }
这时我使用了list2,我指定的集合类型是ArrayList<Integer>,通过反射加入
了"abc"这个String对象,从例2来看,直接System.out.println(list2.get(0));
这样可以很好的打印出abc。但这次我显示地加入了toString()方法。让人吃惊的
是,报错了:Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String at
cn.hxq.generic.ReflyectGeneric.main(ReflyectGeneric.java:16)。
还是和以前一样的异常类型。这样可以证明我前面的推论其实是错的。
想了好久也没想出来,后来请教了群里的同学,同学认为:问题不是出在
toString()是否被调用的前提下,而是在get()这个方法中。
同学的观点:
看看java源代码关于get()方法的定义:
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
这是一个返回值是泛型的方法,编译器在编译的list1.get(0)的时候,因为前面
我声明了List<String> list1,由于去泛型化的原因那么通过list1调用的get()
方法自然就被编译成:
public String get (int index){
return (String) elmentData[index];
}
在运行时,就需要返回一个String类型的值.
而且在return 的时候,会将我加入的Integer强制转换成String类型,于是就报了
java.lang.ClassCastException的错。其实就是类型转换的错误。
新衍生的问题:
那为什么System.out.println(list2.get(0))是正确的,
System.out.println(list2.get(0).toString())却又报错了呢?
如果按照上面的推论:
我声明了List<String> list2,我使用list2调用的get()方法应该会成为这样的形式:
public Integer (int index){
return (Integer) elmentData[index];
}
那么此时将String类型的元素强制转换成Integer的时候却又不报错了呢?再者,为什么一旦显式调用了toString(),却马上报错了?
这些问题实在思考不出来,先丢在一边··。
昨晚没有思考出来,今天早上突然有了灵感。
其实问题是出在get方法的return之后,get()方法本身只是通过return将集合中的引用传递出来
,println()方法接到引用之后,会对接到的引用根据类型去调用编译器在class文件中说明的
toString()方法,因为这个方法是被不同的子类覆写过的。
问题1的解答:
list1.get(0)编译器编译的时候认为它返回的是String值,于是在Class文件中标明了一旦
需要调用toString(),则会使用String类的toString()(实际上如果真是String类,JVM将不会调用)
运行时传递来了一个Integer类的对象,那么就需要调用toString()方法了。
此时JVM就去用一个Integer的对象调用了String类的toString()方法,因此报出类型转换的错误。
Object obj = list.get(0);
这一句很好地说明了get()方法本身内部在进行引用传递时并没有出错。
错在于接受引用的方法或者引用变量。
System.out.println(obj);
此时会调用obj的toString方法肯定是没错的,于是正确打印了。
问题2的解答:
System.out.println(list2.get(0));这样为什么正确呢?
因为println()实际上接到的引用是String类的,JVM没必要再调用编译器在class文件中声明的
Integer类的toString()方法,所以直接就能打印了。
System.out.println(list2.get(0).toString());
这样的显式调用为什么错误?这也是编译器在编译成class文件时造成的,因为
我显式调用了toString(),因为编译器认为我传递出来的是个Integer,所以在class文件
中声明的是Integer类的toString()方法。所以我显示调用时,实际上是用String()类的
对象去调用了Integer类的toString()方法。这样就出错了。
总结如下:
一、
list1Method.invoke(list1, 1);
System.out.println(list1.get(0));
错误:1)、get()方法运行时返回了一个Integer对象,println()方法无法打印。
2)、JVM对Integer对象的处理:去调用toString()方法。
3)、class文件中的信息让它去调用了一个String类的toString()方法。
4)、使用Integer对象调用了String类的toString()方法,出错了。
二、
list1Method.invoke(list2, "abc");
System.out.println(list2.get(0));
正确:1)、get()方法运行时返回了一个String对象,println()方法
接到String类对象。
2)、JVM对String对象的处理:不调用toString()方法。正确了。
三、
list1Method.invoke(list2, "abc");
System.out.println(list2.get(0).toString());
错误:1)、get()方法运行时返回了一个String对象,println()方法
接到String类对象。
2)、此时toString()方法被显式调用,由于class文件得到的类型信息,调用
的是Integer类的toString()方法。
3)、使用String对象去调用Integer类的toString()方法,出错了。
结论是:
使用一个类的对象 去调用了 另一个非父类的toString()方法,导致出错。
如果使用String类,默认是不会调用toString()方法,如果显式调用,会重复上面的
错误。
泛型的应用很广泛,应该牢牢掌握,但如果要深入了解泛型的实现,我想可不是一朝一
夕的事··。
相关文章推荐
- java基础加强--自定义泛型方法及其应用
- Java基础---Java---基础加强---内省的简单运用、注解的定义与反射调用、 自定义注解及其应用、泛型及泛型的高级应用、泛型集合的综合
- Java基础---Java---基础加强---内省的简单运用、注解的定义与反射调用、 自定义注解及其应用、泛型及泛型的高级应用、泛型集合的综合
- 黑马程序员-->Java基础加强-->泛型(Generic)
- 黑马程序员--Java基础加强--13.利用反射操作泛型II【TypeVariable】【GenericArrayType】【WildcardType】【Type及其子接口的来历】【个人总结】
- 黑马程序员-Java基础加强之泛型
- 框架学习前基础加强 泛型高级,注解,反射(泛型&注解)应用案例,IOC,Servlet3.0,动态代理,类加载器
- java基础加强--泛型
- 黑马程序员-java学习基础加强之泛型
- 黑马程序员——基础加强之 Java5的泛型
- Java基础加强总结(二)——泛型
- Java笔记7 Java基础加强<4>泛型
- 黑马程序员 java基础加强--泛型
- Java基础加强之泛型
- Java基础之泛型——使用泛型链表类型(TryGenericLinkedList)
- 【黑马程序员】Java基础加强16:JDK1.5泛型
- JAVA基础加强之泛型
- javaweb-day22-1(基础加强 - 反射泛型、通配符、有限制的通配符)
- java基础加强--泛型
- 黑马程序员---java基础加强---jdk1.5新特性之泛型