Java泛型
所谓泛型,就是变量类型的参数化。泛型是java1.5中引入的一个重要特征,通过引入泛型,可以使编译时类型安全,运行时更少抛出ClassCastException的可能。一提到参数化,最熟悉的就是定义方法是由形参,然后调用此方法时传递实参。那么参数化该怎么理解,顾名思义,就是将类型由原来的具体类型参数化,类似于方法中的变量参数,此是类型也可定义成参数形式,然后在使用,调用时传入具体类型。使用泛型是如果不提供参数类型,即泛型类没有参数化,系统会警告,此时类型为Object。
为什么使用泛型
首先看一段代码:
1 List list=new ArrayList(); 2 list.add("asd"); 3 list.add("qwe"); 4 list.add(123); 5 for(int i=0;i<list.size();i++){ 6 String name=(String) list.get(i);//1 7 System.out.println("name: "+name); 8 }
在循环当中,由于忘记了之前的list中加入了Integer类型的值或者是其他原因,很容易出现类似于//1中的错误,因为在编译阶段正常,但是在运行时会出现ClassCastException异常。当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,该对象的编译类型变成了Object类型,但其运行时类型仍然为其本身类型,因此//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现转化异常。
泛型可以限制加入集合当中的元素类型,是编译时不出现问题,运行时就不会抛出ClassCastException异常。使用泛型的典型例子,是在集合中的泛型使用。
在使用泛型前,存入集合中的元素可以是任何类型的,当从集合中取出时,所有的元素都是Object类型,需要进行向下的强制类型转换,转换到特定的类型。
比如:
List myIntList = new LinkedList(); // 1 myIntList.add(new Integer(0)); // 2 Integer x = (Integer) myIntList.iterator().next(); // 3
第三行的这个强制类型转换可能会引起运行时的错误。
泛型的思想就是由程序员指定类型,这样集合就只能容纳该类型的元素。
使用泛型:
List<Integer> myIntList = new LinkedList<Integer>(); // 1' myIntList.add(new Integer(0)); // 2' Integer x = myIntList.iterator().next(); // 3'
将第三行的强制类型转换变为了第一行的List类型说明,编译器会为我们检查类型的正确性。这样,代码的可读性和健壮性也会增强。
自定义泛型类,泛型接口,泛型方法
先简单定义一个类:
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 System.out.println("name:" + name.getData()); 7 } 8 9 } 10 11 class Box<T> { 12 13 private T data; 14 15 public Box() { 16 17 } 18 19 public Box(T data) { 20 this.data = data; 21 } 22 23 public T getData() { 24 return data; 25 } 26 27 }
在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7 8 System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box 9 System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box 10 System.out.println(name.getClass() == age.getClass()); // true 11 12 } 13 14 }
由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
泛型和子类:
List<String> ls = new ArrayList<String>(); // 1 List<Object> lo = ls; // 2
一个String类型的List是一个Object类的List吗?
不可以,Java编译器将会在第二行产生一个编译错误,因为它们的类型不匹配。
这样就避免了如果lo引入加入Object类型的对象,而ls引用试图将其转换为String类型而引发错误。所以编译器阻止了这种可能。
继承泛型类别:
父类:
public class Parent<T1,T2> { private T1 foo1; private T2 foo2; public T1 getFoo1() { return foo1; } public void setFoo1(T1 foo1) { this.foo1 = foo1; } public T2 getFoo2() { return foo2; } public void setFoo2(T2 foo2) { this.foo2 = foo2; } }
子类继承父类:
public class Child<T1, T2, T3> extends Parent<T1, T2> { private T3 foo3; public T3 getFoo3() { return foo3; } public void setFoo3(T3 foo3) { this.foo3 = foo3; } }
实现泛型接口:
泛型接口:
public interface ParentInterface<T1,T2> { public void setFoo1(T1 foo1); public void setFoo2(T2 foo2); public T1 getFoo1(); public T2 getFoo2(); }
子类实现泛型接口:
public class ChildClass<T1,T2> implements ParentInterface<T1, T2> { private T1 foo1; private T2 foo2; @Override public void setFoo1(T1 foo1) { this.foo1 = foo1; } @Override public void setFoo2(T2 foo2) { this.foo2 = foo2; } @Override public T1 getFoo1() { return this.foo1; } @Override public T2 getFoo2() { return this.foo2; } }
调用泛型方法语法格式如下:
定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。
Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象。
为什么要用变量c来创建对象呢?既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。
泛型方法要求的参数是Class<T>类型,而Class.forName()方法的返回值也是Class<T>,因此可以用Class.forName()作为参数。其中,forName()方法中的参数是何种类型,返回的Class<T>就是何种类型。在本例中,forName()方法中传入的是User类的完整路径,因此返回的是Class<User>类型的对象,因此调用泛型方法时,变量c的类型就是Class<User>,因此泛型方法中的泛型T就被指明为User,因此变量obj的类型为User。
当然,泛型方法不是仅仅可以有一个参数Class<T>,可以根据需要添加其他参数。
为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。
泛型的继承规则:
public void fun(){
System.out.println("A.fun()");
}
}
public class BSub extends B{
@Override
public void fun(){
System.out.println("BSub.fun()");
}
public static void main(String[] args) {
Pair<BSub> p1 = new Pair<BSub>();
Pair p2 = p1;
p2.setFirst(new Date());
BSub b = p1.getFirst();
b.fun();
}
}
Pair<BSub> p1 = new Pair<BSub>();
// Pair<B> p = p1;//不合法,类型不兼容
Pair p2 = p1;
p2.setFirst(new Date());
BSub b = p1.getFirst();
b.fun();
Pair<B> p3 = new Pair<B>();
// Pair<BSub> p4 = p3;//不合法,同样类型不兼容
参考资料:
http://www.cnblogs.com/mengdd/archive/2013/01/21/2869778.html
http://www.cnblogs.com/lwbqqyumidi/p/3837629.html
http://www.cnblogs.com/iyangyuan/archive/2013/04/09/3011274.html
http://www.cnblogs.com/sunwei2012/archive/2010/10/08/1845938.html
http://www.cnblogs.com/Fskjb/archive/2009/08/23/1552506.html
http://www.cnblogs.com/yinhaiming/articles/1749738.html
http://www.cnblogs.com/nerxious/archive/2012/12/21/2828121.html
http://www.cnblogs.com/anrainie/archive/2012/03/09/2387177.html
http://blog.sina.com.cn/s/blog_44c1e6da0100cus8.html