<JDK1.5新特性>:1.泛型-
所谓泛型,顾名思义,“广泛的类型”
就是数据类型的参数化。
类(或接口)是对象实例的抽象,与之类似,泛型是类的抽象;因此泛型通过传入类型实参("实例化")变成为类(或接口),类通过实例化变成具体对象。
因此,ArraList是泛型,ArrayList<String>就是泛型实例化后得到的类,new ArrayList<String>() 就是同过泛型生成的类实例化后得到的对象。
使用泛型的好处之一就是减少了强制类型转换引起的运行时风险,即编译正确则运行一定没有问题思想!!
如果使用泛型,只要代码在编译时没有出现警告,就不会遇到运行时的“ClassCastException”(即类型转换异常,运行时抛出,一般发生在强制类型转换过程中)
因此在能用泛型的地方一定要使用泛型,目的是保证编译无错,运行一定没有相关错误发生。
另外也可以是你的代码更加简洁,否则与应用逻辑无关的强制类型转换会充斥在你的编辑界面中。
Java泛型是JDK1.5新加入的特性,基本意义同C++,定义方式稍有不同
1、单类型参数泛型实现
泛型定义:
package cn.edu.bupt;
public class GenericFoo<Type> {
private Type foo;
public Type getFoo() {
return foo;
}
public void setFoo(Type foo) {
this.foo = foo;
}
}
其中Type代表一种类型的信息,而不是一种具体的类型
Type可以使用其他标识符代替,如T,K等。
泛型的实例化与使用:
package cn.edu.bupt;
public class MainTest {
public static void main(String[] args) {
GenericFoo<String> foo = new GenericFoo<String>();
foo.setFoo("u're foo!");
System.out.println(foo.getFoo());
}
}
当同一泛型使用不同的数据类型实例化的两个对象是不能相互赋值的,如下:
即当使用确定数据类型的泛型之后,数据类型就成为当前泛型的一部分,因此两个数据类型的泛型属于不同的类,
这样生成的两个实例的引用是不能相互赋值的,如上例中,一个是GenericFoo<String>类型,一个是GenericFoo<Boolean>
类型;否则,会出现类型转换错误。
使用泛型的时候不是一定要将数据类型传递进去,也可以不初始化参数,此时不会出现编译错误,但是会出现警
告提示,如下:
由图中可以看出,当不显式输入数据类型的时候,编译器会默认Type为Object类型。
注: Java仅仅支持类类型的泛型,不能将八个原生数据类型传递给泛型,否则会发生编译错误。
2、多类型参数泛型实现
泛型定义:
package cn.edu.bupt;
public class Generic<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;
}
}
泛型使用:
package cn.edu.bupt;
public class MainTest {
public static void main(String[] args) {
Generic<Integer, Boolean> foo = new Generic<Integer, Boolean>();
foo.setFoo1(new Integer(-20));
foo.setFoo2(new Boolean(false));
System.out.println(foo.getFoo1());
System.out.println(foo.getFoo2());
}
}
3、泛型与数组
【方法1】
泛型定义:
package cn.edu.bupt1;
public class Generic2<T> {
private T[] fooArray;
public T[] getFooArray() {
return fooArray;
}
public void setFooArray(T[] fooArray) {
this.fooArray = fooArray;
}
}
泛型使用
package cn.edu.bupt1;
public class MainTest {
public static void main(String[] args) {
Generic2<String> foo = new Generic2<String>();
String[] str1 = {"hello", "world"};
String[] str2 = null;
foo.setFooArray(str1);
str2 = foo.getFooArray();
for(String str: str2) {
System.out.println(str);
}
}
}
【方法2】
泛型定义(同普通泛型定义):
package cn.edu.bupt1;
public class GenericFoo<Type> {
private Type foo;
public Type getFoo() {
return foo;
}
public void setFoo(Type foo) {
this.foo = foo;
}
}
数组泛型的使用:
public class MainTest {
public static void main(String[] args) {
GenericFoo<String[]> foo = new GenericFoo<String[]>();
String[] str1 = {"hello", "world"};
String[] str2 = null;
foo.setFoo(str1);
str2 = foo.getFoo();
for(String str: str2) {
System.out.println(str);
}
}
}
4、泛型的泛型
即一个泛型的类内部的成员类型又是一个泛型类型,具体如下例:
package cn.edu.bupt1;
public class WrapperFoo<T> {
private GenericFoo1<T> foo;
public GenericFoo1<T> getFoo() {
return foo;
}
public void setFoo(GenericFoo1<T> foo) {
this.foo = foo;
}
public static void main(String[] args) {
//构造内部泛型类型实例
GenericFoo1<Integer> foo = new GenericFoo1<Integer>();
foo.setFoo(new Integer(-10));
//构造外部泛型类型实例,此时需要传入内部泛型类型实例
WrapperFoo<Integer> wrapperfoo = new WrapperFoo<Integer>();
wrapperfoo.setFoo(foo);
System.out.println(wrapperfoo.getFoo().getFoo());
}
}
class GenericFoo1<T> {
private T foo;
public T getFoo() {
return foo;
}
public void setFoo(T foo) {
this.foo = foo;
}
}
5、限制使用泛型中的类型
在定义泛型类别的时候,缺省可以使用任何的类型实例化泛型中的类型。但是如果想要限制使用泛型类别
时,即只能用某个特定类型或者是子类型才能实例化该类型时,可以在定义类型时,使用extends关键字指定
这个类型必须是继承某个类,或者实现某个接口。
如下图所示:
当没有指定泛型继承的类型或接口的时候,默认使用"<? extends Object >"(或"<?>"),所以默认情况下任何类型都
可以作为类型参数传入泛型中。
6、调用泛型类但不传递类型参数情况解释
当在使用泛型类的时候若没有传入类型参数的时候,默认类型<T>为<?>或<Object>类型,如下:
等价于:
7、类型通配声明
如下图:
这种情况表示generic可以指向一个GenericTest的实例,但是这个实例里面传递的类型实参必须位于指定的
类的继承层次下面子类中,否则会编译错误。

package cn.edu.bupt1;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class GenericTest<T> {
private T foo;
public T getFoo() {
return foo;
}
public void setFoo(T foo) {
this.foo = foo;
}
public static void main(String[] args) {
GenericTest<? extends List> generic = null;
generic = new GenericTest<ArrayList>();
generic = new GenericTest<HashMap>();
}
}
与extends相对的是super关键字,这种情况表示generic可以指向一个GenericTest的实例,但是这个实
例里面传递的泛型类型参数必须位于指定的类的继承层次上面父类中,否则会编译错误。
这种情况使用的比较少
如下图:

package cn.edu.bupt1;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class GenericTest<T> {
private T foo;
public T getFoo() {
return foo;
}
public void setFoo(T foo) {
this.foo = foo;
}
public static void main(String[] args) {
GenericTest<? super List> generic = null;
generic = new GenericTest<Object>();
generic = new GenericTest<ArrayList>();
}
}
请将这种情况与第5种情况进行区分。
举例:
package cn.edu.bupt1;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class GenericTest<T> {
private T foo;
public T getFoo() {
return foo;
}
public void setFoo(T foo) {
this.foo = foo;
}
public static void main(String[] args) {
//generic 是指向GenericTest<? extends Object>类型的引用变量
GenericTest<? extends Object> generic = null;
//foo 是指向GenericTest<String>类型的引用变量
GenericTest<String> foo = new GenericTest<String>();
foo.setFoo("Hello world");
System.out.println(foo.getFoo());
//二者可以进行赋值
generic = foo;
}
}
特殊情况(重要):
使用<?>或者是<? extends SomeClass>(两者意义相同)的声明方式,意味着您只
能通过该名称来取得所参考类的实例的信息,或者是移除某些信息,但不能增加它的信息,
因为只知道当中放置的是SomeClass的子类,但不能确定具体是什么子类的实例。编译器不
让您加入信息,理由是:如果可以加入信息的话,那么你就得记得取回的实例是什么类
型,然后转换为原来的类型方可进行操作,但是这就需要进行强制类型转换,也就失去
了使用泛型的意义,因为泛型本身是不让你进行强制类型转换的。
解释:因为编译器只知道generic变量指向的对象中的泛型是Object或其子类,但是并不知道真正的类
是什么。那么如果可是执行上图中第37行,编译器就认为程序员知道设置进去的值时什么类型(例如:
Integer类型),在程序员取出该值的时候又需要将Object类型的值强制转换成为Integer类型,但是
泛型设计的初衷是不让你进行强制类型转换。
重点是generic引用变量在编译的时候是GenericTest<? extends Object>类型,即传入的类型时
Object或其子类型但是编译器并不知道传入的子类的具体类型时什么.,只有在执行的时候才能知道。
另:GenericTest<? extends Object>等价于 GenericTest<?>
在JDK中很多类都是这样写法.
8.泛型与匿名内部类:
定义与调用:
package cn.edu.bupt.personsort2;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class PersonSortByAge implements PersonSort {
@Override
public void sort(List<Person> persons) {
// TODO Auto-generated method stub
Collections.sort(persons, new MyComparator());
}
//匿名内部类
public class MyComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
// TODO Auto-generated method stub
int tag = 0;
tag = p1.getAge() - p2.getAge();
if(tag == 0) {
return p1.getId() - p2.getId();
}
return tag;
}
}
}
9.泛型类的继承
父类定义:Parent.java
package cn.edu.bupt.genericinterface;
public class Parent<T1, T2> {
private T1 foo1 = null;
private T2 foo2 = null;
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;
}
}
子类定义:Child.java
package cn.edu.bupt.genericinterface;
public class Child<T1, T2, T3> extends Parent<T1, T2> {
private T3 foo3 = null;
public T3 getFoo3() {
return foo3;
}
public void setFoo3(T3 foo3) {
this.foo3 = foo3;
}
}
泛型子类的使用:
package cn.edu.bupt.genericinterface;
public class MainTest {
public static void main(String[] args) {
Child<String, String, String> childs = new Child<String, String, String>();
childs.setFoo1("zhangsan");
childs.setFoo2("lisi");
childs.setFoo3("wangwu");
System.out.println(childs.getFoo1());
System.out.println(childs.getFoo2());
System.out.println(childs.getFoo3());
}
}
子类中必须要设置父类中的泛型类型T1,T2,因为在为子类生成对象的时候肯定要同时为父类分配相应的空间
因此必须要指定类型参数才能指派空间,在这里Child.java文件中的Parent中的T1和T2是实际类型参数
,是由Child中的T1,T2形式类型参数传递过来的;Child中的T1,T2,T3则真正代表的是形式类型参数,需要在进行
对象实例化的时候传递实际类型参数,如:String,Integer等。其中原理类似于普通参数传递过程。因此可以
这样写:
package cn.edu.bupt.genericinterface;
public class Child<T1, T2> extends Parent<T1, String> {
private T2 foo3 = null;
public T2 getFoo3() {
return foo3;
}
public void setFoo3(T2 foo3) {
this.foo3 = foo3;
}
}
这里Child中的T2与Parent中的T2已经不是一个类型了,但是有相同的名称,虽然并不建议这么写(容易引起混淆),
但这里是为了看清关系。
注意:Child中的T2是泛型的新建的形式类型参数,Parent中原先的T2此时已经被实际类型参数String替代。
这里的Parent<T1, String>已经被实例化为一个具体类,而不再是泛型,其中的T1已经变成实际类型参数。
即在上边代码中,Child<T1, T2>是属于泛型的定义,而Parent<T1, String>是输入Parent泛型的使用,已经通过
类型参数T1和String将泛型Parent<T1, T2>实例化成为一个具体的类了,注意泛型机制的理解。
在为子类生成实例的时候一定要同时为T1,T2,T3传递实际类型参数,不能只传递T3的类型参数,原因同上。
10.泛型接口的实现
与泛型类的继承相类似,即泛型不仅可以应用于类中,也可以应用于接口中。有如下代码:
ParentInterface.java
package cn.edu.bupt.genericinterface;
public interface ParentInterface<T1, T2> {
void setFoo1(T1 foo1);
void setFoo2(T2 foo2);
T1 getFoo1();
T2 getFoo2();
}
ChildClass.java
package cn.edu.bupt.genericinterface;
public class ChildClass<T1> implements ParentInterface<T1, String> {
private T1 foo1 = null;
private String foo2 = null;
@Override
public void setFoo1(T1 foo1) {
// TODO Auto-generated method stub
this.foo1 = foo1;
}
@Override
public void setFoo2(String foo2) {
// TODO Auto-generated method stub
this.foo2 = foo2;
}
@Override
public T1 getFoo1() {
// TODO Auto-generated method stub
return this.foo1;
}
@Override
public String getFoo2() {
// TODO Auto-generated method stub
return this.foo2;
}
}
在ChildClass.java文件中,ParentInterface已经被实例化为接口,而不再是泛型接口,两个用于
实例化的实际类型参数分别为从ChildClass<T1>中传递过来的T1,和String类型。
11.泛型的不协变性
首先,数组时协变的,例如:
如果A extends B,那么A的数组也是B的数组,并且完全可以在需要B(父类)的地方使用A(子类):
package cn.edu.bupt1;
public class Arrayxiebian {
public static void main(String[] args) {
Integer[] intArray = new Integer[10]; //子类数组定义
Number[] numArray = intArray; //父类数组使用子类数组地址赋值
}
}
这段代码是有效的,因为一个Integer 是 Number,因此Integer[] 也是 Number[],从逻辑上来讲是合理的。
但是对于泛型,则相反。一个常见的混淆是假设它们像数组一样是协变的。其实他们不是协变的。
List<Object> 并不是 List<String>的父类。
以下描述摘自http://hi.baidu.com/hcjfy/blog/item/166e6ec6cb5742199c163d58.html
让我们测试一下我们对泛型的理解。下面的代码片断合法么?
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2
第1行当然合法,但是这个问题的狡猾之处在于第2行。
这产生一个问题:
一个String的List是一个Object的List么?大多数人的直觉是回答:“当然!”。
好,在看下面的几行:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: 试图把Object赋值给String
这里,我们使用lo指向ls。我们通过lo来访问ls,一个String的list。我们可以插入任意对象进去。结果是ls中保存的不再是String。当我们试图从中取出元素的时候,会得到意外的结果。
java编译器当然会阻止这种情况的发生。第2行会导致一个编译错误。
总之,如果Foo是Bar的一个子类型(子类或者子接口),而G是某种泛型声明,那么G<Foo>是G<Bar>的子类型并不成立!!
这可能是你学习泛型中最难理解的部分,因为它和你的直觉相反。
这种直觉的问题在于它假定这个集合不改变。我们的直觉认为这些东西都不可改变。
举例来说,如果一个交通部(DMV)提供一个驾驶员里表给人口普查局,这似乎很合理。我们想,一个List<Driver>是一个List<Person>,假定Driver是Person的子类型。实际上,我们传递的是一个驾驶员注册的拷贝。然而,人口普查局可能往驾驶员list中加入其他人,这破坏了交通部的记录。
12.通配符(Wildcards)
考虑写一个例程来打印一个集合(Collection)中的所有元素。下面是在老的语言中你可能写的代码:
void printCollection(Collection c) {
for(Iterator itr = c.iterator(); itr.hasNext(); ) {
System.out.println(itr.next());
}
}
下面使用泛型的幼稚的尝试
void printCollection(Collection<Object> c) {
for(Object o : c) {
System.out.println(o);
}
}
这里的问题是:新版本的用途比老版本小了很多。老版本的代码可以使用任何类型的Collection作为参数,而新版本只能使用Collection<Object>,(这是因为泛型的不协变性,见11),它并不是所有类型的Collection的父类。
那么什么是各种Collection类型的父类呢?是Collection<?>(读作"collection of unknown"),就是,一个集合,它的元素类型可以匹配任何类型,它是所有类型的Collection的父类。显然它被称作通配符。因此程序可以变成这样:
void printCollection(Collection<?> c) {
for(Object o : c) {
System.out.println(o);
}
}
这样,这种形式和不适用泛型的Collection意义相同。
现在,我们可以将任何类型的Collection传递给它。注意我们仍然可以读取c中的元素,其读取的元素的类型是Object类型。这永远是安全的,因为不管传递的Collection的类型是什么,它包含的元素一定是Object的子类对象。
但是,将任意元素加入到其中不是类型安全的。
public static void main(String[] args) {
Collection<?> c = new ArrayList<String>();
c.add(new Object()); //compile error
}
add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那具体是一个什么类型,所以我们无法传任何对象进去。唯一例外的是null。
另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我知道,它总是一个Object,因此把get的返回值赋值给一个Object类型的变量或者放在任何希望是Object类型的地方是安全的。可以是用Object来引用对象中的数据。
13.关于返回T<?>的成员函数
在学习反射机制时,发现Class类中的有些函数返回的是Class<?>类型,一开始看见实在是有点纳闷,经过几天的学习发现还是有些规律的。
这种方法的意思是,返回的值是属于Class<?>类型的,也就是可以使用该返回值为一个Class<?>类型的变量进行赋值,并且可以通过Class<?>的方式对外进行操作,而不是具体的某一种类型,如:Class<String>、Class<Integer>等等。
例如:
package cn.edu.bupt.reflect;
import java.lang.reflect.Method;
public class InvokeTester {
public int add(int param1, int param2) {
return param1 + param2;
}
public String echo(String message) {
return "Hello: " + message;
}
public static void main(String[] args) throws Exception{
//编译时调用成员方法
InvokeTester it = new InvokeTester();
System.out.println(it.add(1, 2));
System.out.println(it.echo("world"));
System.out.println("===========================");
//通过反射的方式调用成员方法,可以使用Class<?>接收返回值也可以使用Class<InvokeTester>接收,具体参见JDK
Class<?> classType = InvokeTester.class;
Object invokeTester = classType.newInstance();
System.out.println(invokeTester instanceof InvokeTester);
Method addMethod = classType.getMethod("add", new Class<?>[]{int.class, int.class});
int result = (Integer) addMethod.invoke(invokeTester, 1, 2);
System.out.println(result);
System.out.println("===========================");
Class<InvokeTester> classType1 = InvokeTester.class;
InvokeTester invokeTester1 = classType1.newInstance();
Method addMethod1 = classType1.getMethod("add", int.class, int.class);
int result1 = (Integer)addMethod1.invoke(invokeTester1, 1, 4);
System.out.println(result1);
}
}
泛型集合举例:
a> 简单集合类的实现:
泛型集合类的定义:
package cn.edu.bupt;
public class SimpleCollection<T> {
private Object[] objArr;
private int index = 0;
public SimpleCollection() {
super();
this.objArr = new Object[10];
}
public SimpleCollection(int capacity) {
super();
this.objArr = new Object[capacity];
}
public void add(T t) {
this.objArr[this.index++] = t;
}
public int getLen() {
return this.index;
}
public T get(int i) {
return (T)this.objArr[i];
}
}
泛型集合类的调用:
package cn.edu.bupt;
public class MainTest {
public static void main(String[] args) {
SimpleCollection<Integer> c = new SimpleCollection<Integer>();
for(int i=0; i<10; i++) {
c.add(new Integer(i));
}
for(int i=0; i<10; i++) {
Integer in = c.get(i);
System.out.println(in.toString());
}
}
}
泛型注意事项:
- 不能使用泛型类型创建数组,如下:
这是因为泛型类型的大小不固定,无法使用new在内存中为泛型类型分配固定的内存大小。
解决方法有两种
1>使用首先创建Object[]数组,然后强制转换成为T[]数组类型,然后赋值给objArr变量,如下:
package cn.edu.bupt1;
public class SimpleCollection<T> {
private T[] objArr;
private int index = 0;
public SimpleCollection() {
super();
this.objArr = (T[])new Object[10];
}
}
2>将objArr设置成为Object[]类型,创建数组时也使用new Object[10],不同的仅仅在加入和取出
元素的时候使用强制类型转换成为T(推荐)。
package cn.edu.bupt1;
public class SimpleCollection<T> {
private Object[] objArr;
private int index = 0;
public SimpleCollection() {
super();
this.objArr = new Object[10];
}
public SimpleCollection(int capacity) {
super();
this.objArr = new Object[capacity];
}
public void add(T t) {
this.objArr[this.index++] = t;
}
public int getLen() {
return this.index;
}
@SuppressWarnings("unchecked")
public T get(int i) {
return (T)this.objArr[i];
}
}