之前对泛型就是停留在会使用而已,今天把泛型的一些重要的例子记录一下以及对深层次的理解一下。
泛型小例子:
package zsc.lian.test;
import java.lang.reflect.InvocationTargetException;
import java.text.Annotation;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class CollectionTest {
public static void main(String[] args) throws IllegalArgumentException,
SecurityException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
/*
* 错误一 ,道理很直观,从编译器的角度上来说li原本是可以装任意类型的数据,
* 但是在实际内存可以装整型的数据,这样就产生了矛盾。因为在后面取出数据
* 时,就只可以取出整型类型的数据,但是我们规定却可以取出任意类型的数据, 所以矛盾,在编译期间就会报错
*/
// Mylist<Object> li = new Mylist<Integer>();
// 纠正Mylist<Object> li = new Mylist();
/*
* 错误二,道理仍然很直观,、从编译器角度来说li可以装整型的数据,但是实际上
* 在内存可以存放任意类型的数据,那么当取出数据时,取出的数据就什么类型都可以,
* 但是我们规定只能取出Integer类型的数据,所以就矛盾,编译就不通过。
*/
// Mylist<Integer> li = new Mylist<Object>();
// 纠正Mylist<Integer> li = new Mylist();
/*
* 错误三,不能使用这种List<String>[] lsa = new ArrayList<String>[10];
* List<String>[] lsa = new ArrayList<String>[10]; // illegal Object[]
* oa = lsa; // OK because List<String> is a subtype of Object
* List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3));
* oa[0] = li; String s = lsa[0].get(0); 最后一行将抛出
* ClassCastException,因为这样将把 List<Integer>填入本应是 List<String>的位置。
* 因为数组协变会破坏泛型的类型安全,所以不允许实例化泛型类型的数组(除非类型参数是未绑定的通配符,比如 List<?>)
*/
// 纠正List<?>[] lsa = new List<?>[3];
/*
* 第一种方式,利用这种骗过编译器
*/
Mylist ii = new Mylist<Integer>();
Mylist<Object> mylist = ii;
mylist.add("33");
mylist.PrintVaule();
/*
* 第二种方式,利用反射绕过编译器
*/
Mylist<Integer> li1 = new Mylist<Integer>();
li1.getClass().getMethod("add", Object.class).invoke(li1, "adf");
li1.PrintVaule();
printMyList(li1);
// 通配符扩展
/*
* 限定通配符的上边界
*/
Mylist<? extends Number> x = new Mylist<Integer>();// right,因为Integer继承了Number
// Mylist<? extends Number> x1 = new Mylist<String>(); wrong
/*
* 限定通配符的下边界
*/
Mylist<? super Integer> x2 = new Mylist<Number>();// right,因为Number是Integer的父类
// Mylist<? super String> x3 = new Mylist<Number>();wrong
try {
Class<?> c = Class.forName("java.lang.String");
System.out.println(c);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/*
* 将父类的class对象强制转成子类对象。不能是其他对象,必须是子类的
*/
Class<? extends Number> cs = Integer.class.asSubclass(Number.class);
System.out.println(cs.getCanonicalName());
A a = new A();
System.out.println(a.getClass().getName());
Class<? extends A> cs1 = B.class.asSubclass(A.class);
System.out.println(cs1.getName());
//调用change方法
change(new String[]{"asdfa","sdfas","sdfe"},1,2);
}
/*
* 报错四,原因见上面错误一二 public void printMyList(Mylist<Object> li1){
*
* }
*/
/*
* 报错五,因为不知传入过来的是什么类型,万一传入一个Mylist<Integer> 的呢?那么我们就无法li1.add("asd") public
* static void printMyList(Mylist<?> li1){ li1.add("adfd"); }
*/
//解决方法
public static <T> void printMyList1(Mylist<T> list,T obj){
list.add(obj);
}
/*
* ?号是通配符,可以指向任意类型
*/
public static void printMyList(Mylist<?> li1) {
li1 = new Mylist<Date>();
}
/*
* 定义类似C++模版,对任意类型的数组交换位置
*/
public static <T> void change(T[] array,int i,int j){
T k = array[i];
array[i] = array[j];
array[j] = k;
}
}
class A{
}
class B extends A{
}
class C extends A{
/*
* 只能返回A的子类,返回其他类型报错
*/
public <Y extends A> Y getMyA(Class<Y> A){
return (Y) new B();
}
}
class Mylist<I> {
Object o;
Object o1;
public Mylist() {
}
public Mylist(I i) {
o = i;
System.out.println(o);
}
public void add(I e) {
o1 = e;
}
public void PrintVaule() {
System.out.println(o1.getClass().getName());
}
}
泛型例子二:
package zsc.lian.test;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Vector;
//class GenericDao {
// public <T> void save(T t){
//
// }
//
// public <T> T findId(int id){
// return null;
// }
//}
//解决方法
class GenericDao<T> {
public void save(T t){
}
public T findId(int id){
return null;
}
public static void applyVector(Vector<Date> v){
}
//错误,静态方法不能这样使用泛型
// public static void update(T obj){
//
// }
}
class Person{
}
public class Test{
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
/*
* 插入和返回的数据类型不一致,即添入是什么
* 类型的数据,取出应该就是什么类型的数据
*/
GenericDao<Person> Gd = new GenericDao<Person>();
Gd.save(new Person());
Person s = Gd.findId(1);
for (int i = 0; i < 10; i++) ; //为了打印StackMapTable信息
Method method = Gd.getClass().getMethod("applyVector",Vector.class);
Type[] type = method.getGenericParameterTypes();
ParameterizedType pt = (ParameterizedType) type[0];
System.out.println(pt.getActualTypeArguments()[0]);
}
}
之前对一直对这个例子很迷惑,为什么会迷惑,因为,总所周知,泛型只是做给编译器看的而已,也就是说由编译器编译后泛型信息会被擦除,class文件不会保存泛型的信息。那么上面那个例子GenericDao类中怎么可以通过反射来确定Vector可以装Date类型的数据呢?难道并且没有擦除,于是用了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: Test.java
package zsc.lian.test;
import java.util.Vector;
class GenericDao
{
GenericDao()
{
}
public void save(Object obj)
{
}
public Object findId(int id)
{
return null;
}
public static void applyVector(Vector vector)
{
}
}
查看到GenericDao.Class文件中确实没有保存泛型的信息,确实是被擦除了。那原因是处在哪里呢?请教了几个工作多年的朋友,也没有得到很好的回答。后来在网上搜到一篇不错的文章:http://rednaxelafx.iteye.com/blog/586212
里面说道:
位于声明一侧的,源码里写了什么到运行时就能看到什么;
位于使用一侧的,源码里写什么到运行时都没了。
具体可以查看此文章。
确实,在java 5,Class文件中确实加入了泛型的信息。但是只是在声明一侧加入了。
查看Test.Class的数据类型:
StackMapTable: number_of_entries = 2
frame_type = 254 /* append */
offset_delta = 33
locals = [ class zsc/lian/test/GenericDao, class zsc/lian/test/Person, int ]
frame_type = 2 /* same */
从上面看泛型信息被擦除了,接着看看GenericDao.Class的信息
public static void applyVector(java.util.Vector<java.util.Date>);
flags: ACC_PUBLIC, ACC_STATIC
Signature: #30 // (Ljava/util/Vector<Ljava/util/Date;>;)V
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 31: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 v Ljava/util/Vector;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 1 0 v Ljava/util/Vector<Ljava/util/Date;>;
}
既然保存了泛型的信息,那么就可以通过反射获取泛型的具体类型。