- 浏览: 64469 次
- 性别:
- 来自: 上海
最新评论
-
sunkiller:
而最后的filter里面Object dataSourceNa ...
Spring 动态设置数据源 -
bestree007:
很好,就是方法返回类型之前的<T> ...
泛型-泛型简介 -
J_Love_me:
很好,很强大。
泛型-泛型简介 -
Javakeith:
很好!解释很细致!
泛型-泛型简介 -
q821424508:
经典啊 应用之 还是一楼啊
泛型-泛型简介
泛型简介
先拿一个例子来说明泛型是什么。
有两个类如下,要构造两个类的对象,并打印出各自的成员x。
public class StringFoo {
private String x;
public String getX() {
return x;
}
public void setX(String x) {
this.x = x;
}
}
public class DoubleFoo {
private Double x;
public Double getX() {
return x;
}
public void setX(Double x) {
this.x = x;
}
}
如果要实现对Integer、Long、Date等类型的操作,还要写相应的类,实在是无聊之极。
因此,对上面的两个类进行重构,写成一个类,考虑如下:
上面的类中,成员和方法的逻辑都一样,就是类型不一样。Object是所有类的父类,因此可以考虑用Object做为成员类型,这样就可以实现通用了。
public class ObjectFoo {
private Object x;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
}
调用的代码如下:
public class ObjectFooDemo {
public static void main(String args[]) {
ObjectFoo strFoo = new ObjectFoo();
strFoo.setX("Hello Generics!");
ObjectFoo douFoo = new ObjectFoo();
douFoo.setX(new Double("33"));
ObjectFoo objFoo = new ObjectFoo();
objFoo.setX(new Object());
String str = (String)strFoo.getX();
Double d = (Double)douFoo.getX();
Object obj = objFoo.getX();
System.out.println("strFoo.getX=" + str);
System.out.println("douFoo.getX=" + d);
System.out.println("strFoo.getX=" + obj);
}
}
以上,是没有泛型的情况下,我们编写的代码,采用最顶层基类Object进行类型声明,然后将值传入,取出时要进行强制类型转换。
JDK 从1.5 开始引入了泛型的概念,来优雅解决此类问题。采用泛型技术,编写的代码如下:
public class GenericsFoo <T> {
private T x;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
}
调用的代码如下:
public class GenericsFooDemo {
public static void main(String args[]){
GenericsFoo<String> strFoo=new GenericsFoo<String>();
strFoo.setX("Hello Generics!");
GenericsFoo<Double> douFoo=new GenericsFoo<Double>();
douFoo.setX(new Double("33");
GenericsFoo<Object> objFoo=new GenericsFoo<Object>();
objFoo.setX(new Object());
String str = strFoo.getX();
Double d = douFoo.getX();
Object obj = objFoo.getX();
System.out.println("strFoo.getX=" + str);
System.out.println("douFoo.getX=" + d);
System.out.println("strFoo.getX=" + obj);
}
}
注意,有几点明显的改变:
1. 对象创建时,明确给出类型,如GenericsFoo<String>。
2. 对象通过getX方法取出时,不需要进行类型转换。
3. 对各个方法的调用,如果参数类型与创建时指定的类型不匹配时,编译器就会报错。
那么我们为什么要泛型呢? 有两个好处:
1. 可以在编译时检查存储的数据是否正确。我们开发有一个趋向就是尽早的发现错误,最好就是在编译阶段, 泛型正好符合这一条件。
2. 减少了强制转换, String str = (String)strList.get(0);这样的操作属于一种向下转型, 是比较危险的操作, 当List内存储的对象不适String时就会抛出异常。
JDK1.5 中,java.util 包中的各种数据类型工具类,都支持泛型,在编程中被广泛使用,需要好好掌握。
泛型最常见的应用是应用在类、接口和方法上,下面分别介绍。
3.4.2 泛型应用在接口上:
public interface ValuePair<A,B> {
public A getA();
public B getB();
public String toString();
}
这里A和B都是代表类型。尖角号<>中,可以使用一个类型,也可以使用多个类型。
3.4.3 泛型应用在类上:
public class ValuePairImpl<A,B> {
public final A first;
public final B second;
public ValuePairImpl(A a, B b) { first = a; second = b; }
public A getA() { return first; }
public B getB() { return second; }
public String toString() {
return "(" + first + ", " + second + ")";
}
}
如果这个类实现泛型接口,则相应的写法为:
public class ValuePairImpl<A,B> implements ValuePair<A, B> {
……
}
3.4.4 泛型应用在方法上:
泛型也可以应用在单独的方法上,示例如下:
public class GenericMethod {
public <T> void printValue(T v) {
String str = v.getClass().getName() + “ = “ + v.toString();
System.out.println(str);
}
}
注意语法:在public修饰符后面是<>, 然后是函数返回值, 接着是函数名,函数参数。当然,返回值也可以是泛型的类型。
3.4.5 限制泛型的可用类型
以上介绍的三种泛型应用,应用在接口、类、方法上,是一种通用的做法,对泛型可以传入的类型没有任何限制。但有些场景下,我们希望对可用的类型进行限制,比如希望传入的类型必须从某个类继承(也就是说,必须是某个类的子类、孙类等),这种情况下就用到了泛型限制的语法。
extends:限制泛型类型必须为某个类的后代,包括本类型。
语法:<T extends parentClass>
这里,T为泛型类型,extends 关键字限制泛型类型必须是parentClass的后代。parentClass 指定父类的类型,也可以是接口。
在Java语言中,对类只能单继承,对接口可以多继承,如果要限制指定类型必须从某个类继承,并且实现了多个接口,则语法为:
<T extends parentClass & parentInterface1 & parentInterface2>
注意,类必须在接口前面。
举例如下:
public class BaseClass {
int value;
public BaseClass(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
public class SubClass extends BaseClass{
public SubClass(int value) {
super(value*2);
}
}
public class GenericBound<T extends BaseClass> {
public long sum(List<T> tList) {
long iValue = 0;
for (BaseClass base : tList) {
iValue += base.getValue();
}
return iValue;
}
public static void main(String[] args) {
GenericBound<SubClass> obj = new
GenericBound<SubClass>();
List<SubClass> list = new LinkedList<SubClass>();
list.add(new SubClass(5));
list.add(new SubClass(6));
System.out.println(obj.sum(list));
}
}
运行,输出结果为22.
接着,我们再深入探讨一下。把上面的例子该写如下:
public class GenericBound<T extends BaseClass> {
public long sum(List<T> tList) {
long iValue = 0;
for (BaseClass base : tList) {
iValue += base.getValue();
}
return iValue;
}
public static void main(String[] args) {
// 注意!!!
// 将obj 的类型由GenericBound<SubClass>改为GenericBound<BaseClass>,无法通过编译
GenericBound<BaseClass> obj = new
GenericBound<SubClass>();
List<SubClass> list = new LinkedList<SubClass>();
list.add(new SubClass(5));
list.add(new SubClass(6));
System.out.println(obj.sum(list));
}
}
语句GenericBound<BaseClass> obj = new GenericBound<SubClass>(); 无法通过编译,其根本原因在于,GenericBound类声明处的<T extends BaseClass>,限制了构造此类实例的时候T是确定的一个类型,这个类型是BaseClass的后代。但是BaseClass的后代还又很多,如SubClass3,SubClass4,如果针对每一种都要写出具体的子类类型,那也太麻烦了,干脆还不如用Object通用一下。能不能象普通类那样,用父类的类型引入各种子类的实例,这样不就简单了很多?答案是肯定的,泛型针对这种情况提供了更好的解决方案,那就是“通配符泛型”,下面详细讲解。
3.4.6 通配符泛型
Java 的泛型类型如同 java.lang.String,java.io.File 一样,属于普通的 Java 类型。比方说,下面两个变量的类型就是互不相同的:
Box<Object> boxObj = new Box<Object>();
Box<String> boxStr = new Box<String>();
虽然 String 是 Object 的子类,但是 Box<String> 和 Box<Object> 之间并没有什么关系——Box<String> 不是 Box<Object> 的子类或者子类型,因此,以下赋值语句是非法的:
boxObj = boxStr; // 无法通过编译
因此,我们希望使用泛型时,能象普通类那样,用父类的类型引入各种子类的实例,从而简化程序的开发。Java的泛型中,提供 ? 通配符来满足这个要求。
代码示例如下:
public class WildcardGeneric {
public void print(List<?> lst) {
for (int i = 0; i < lst.size(); i++) {
System.out.println(lst.get(i));
}
}
public static void main(String[] args) {
WildcardGeneric wg = new WildcardGeneric();
ArrayList<String> strList = new ArrayList<String>();
strList.add("One");
strList.add("Two");
wg.print(strList);
LinkedList<Integer> intList = new LinkedList<Integer>();
intList.add(25);
intList.add(30);
wg.print(intList);
}
}
但是这种情况下,WildcardGeneric.print 方法的参数可以接受类型可能对于程序员设计的意图而言太广泛了一点。因为我们可能只是希望 print 可以接受一个List,但这个List中的元素必须是Number的后代。因此,我们要对通配符有所限制,这时可以使用边界通配符(bounded wildcard)形式来满足这个要求。我们将 print 方法再修改一下:
public void print(List<? extends Number> lst) {
for (int i = 0; i < lst.size(); i++) {
System.out.println(lst.get(i));
}
}
这样,List<Integer>、List<Short> 等等类型的变量就可以传给 print 方法,而储存其他类型元素的 List 的泛型类型变量(如List<String>)传给 print 方法将是非法的。
除了 ?extends上边界通配符(upper bounded wildcard)以外,我们还可以使用下边界通配符(lower bounded wildcard),例如 List<? super ViewWindow>。
最后总结一下使用通配符的泛型类型的三种形式:
GenericType<?>
GenericType<? extends upperBoundType>
GenericType<? super lowerBoundType>
3.4.7 泛型深入
我们已经初步掌握了泛型的基本用法,接着再来探讨一下深入的主题。
我们还是先来看一段代码:
public class GenericsFoo <T> {
private T x;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public static void main(String[] args) {
GenericsFoo<String> gf = new GenericsFoo<String>();
gf.setX("Hello");
GenericsFoo<?> gf2 = gf;
gf2.setX("World"); // 报错!!!
String str = gf2.getX(); // 报错!!!
gf2.setX(gf2.getX()); // 报错!!!
}
}
注意,main 方法中的最后三行都是非法的,无法通过编译。本来是一个<String>的泛型,通过<?>来引用后,setX() 传入一个String就报错,getX() 返回值的类型也不是String。更为奇怪的是,语句gf2.setX(gf2.getX()); 就是从里面取出值然后再原封不动设置回去,也不行。这是怎么回事?
为了彻底弄清楚这些问题,我们需要了解JDK对泛型的内部实现原理。先看两个例子:
public class GenericClassTest {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
Class c3 = new ArrayList().getClass();
System.out.println(c1 == c3);
}
}
运行后,输出结果为:
true
true
这个例子说明,泛型ArrayList<String>、ArrayList<Integer>和没有使用泛型的ArrayList 其实是同一个类。就如同没使用泛型一样。
再看第二个例子:
class Element {}
class Box<T> {}
class Pair<KEY, VALUE> {}
public class GenericClassTest2 {
public static void main(String[] args) {
List<Element> list = new ArrayList<Element>();
Map<String,Element> map = new HashMap<String, Element>();
Box<Element> box = new Box<Element>();
Pair<Integer, String> p = new Pair<Integer, String>();
System.out.println(Arrays.toString(
list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
box.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
p.getClass().getTypeParameters()));
}
}
运行后,输出结果为:
[E]
[K, V]
[T]
[KEY, VALUE]
查阅JDK的文档,Class.getTypeParameters() 方法返回一个TypeVariable对象的数组,数组中每个TypeVariable对象描述了泛型中声明的类型。这似乎意味着,我们可以从TypeVariable 对象中找出泛型实例化时真正的类型。但是,从程序运行的输出结果,我们可以看出,Class.getTypeParameters() 返回的一系列TypeVariable 对象,仅仅表征了泛型声明时的参数化类型占位符,真正实例化时的类型都被抛弃了。因此,Java 泛型的真相是:
在泛型代码中,根本就没有参数化类型的信息。
产生这样一个事实的原因在于,JDK 对泛型的内部实现,采用了擦除(erasure)的方式,具体擦除的方式如下:
1) ArrayList<String>、ArrayList<Integer>、ArrayList<?>都被擦除成ArrayList
2) ArrayList<T extends BaseClass>、ArrayList<? extends BaseClass>都被擦除成ArrayList<BaseClass>
3) ArrayList<? super BaseClass>被擦除成ArrayList<BaseClass>
理解了擦除的实现机制后,我们再回过头来,分析一下前面的例子,看看为什么不能通过编译:
public class GenericsFoo <T> {
private T x;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public static void main(String[] args) {
GenericsFoo<String> gf = new GenericsFoo<String>();
gf.setX("Hello");
GenericsFoo<?> gf2 = gf;
gf2.setX("World"); // 报错!!!
String str = gf2.getX(); // 报错!!!
gf2.setX(gf2.getX()); // 报错!!!
}
}
由于擦除机制,GenericsFoo<?>都被擦除成GenericsFoo,类型被丢失后,那么相应的方法声明就变成了:
public Object getX();
public void setX(null x);
因此,泛型类的任何取出值的get方法,返回值都变成了Object,可以被调用,但返回值需要进行类型转换;泛型类的任何设置值的set方法,参数类型都变成了null,任何类型都无法转换成null类型,因此所有的set方法都无法被调用。
这样就形成了一个有趣的现象:对<?>的泛型类型,只能get,不能set。
以上阐述了Java泛型的擦除机制,导致一些有用的类型信息丢失。但我们可以通过一些技巧,让编译器重新构建出类型信息,从而使得set方法可以被正常调用。见如下的代码:
public void setGeneric(GenericsFoo<?> foo) {
setGenericHelper(foo);
}
private<V> void setGenericHelper(GenericsFoo<V> foo) {
foo.setX(foo.getX());
}
setGenericHelper() 是一个泛型方法,泛型方法引入了额外的类型参数(位于返回类型之前的尖括号中),这些参数用于表示参数和/或方法的返回值之间的类型约束。setGenericHelper () 这种声明方式,允许编译器(通过类型接口)对GenericsFoo 泛型的类型参数命名。但一个类型可以有父类、祖父类,还可以实现多个接口,那么编译器会转换成哪个类型呢?
我们用下面这段代码来验证一下:
public class GenericClassTest3 {
public static<T> String getType(T arg) {
return arg.getClass().getName();
}
public static void main(String[] args) {
Integer i = new Integer(5);
System.out.println(GenericClassTest3.getType(i));
}
}
程序运行后,输出结果为:
java.lang.Integer
因此,编译器能够推断 T 是 Integer、Number、 Serializable 或 Object,但它选择 Integer 作为满足约束的最具体类型。
另外,由于泛型的擦除机制,我们也无法直接对泛型类型用new操作符,比如:
public class GenericNew<T> {
public T create() {
T obj = new T(); // 无法通过编译!!!
return obj;
}
}
由于擦除机制,语句T obj = new T(); 擦除后就变成了
obj = new ();
于是就无法通过编译了。
但是,在一些情况下,我们还是需要对泛型类型的动态实例化。对于创建单个对象和创建数组,代码示例如下:
public class GenericNew<T> {
public T create(Class<T> cls) {
try {
Object obj = cls.newInstance();
return (T)obj;
} catch(Exception e) {
return null;
}
}
public T[] createArray(Class<T> cls, int len) {
try {
Object obj = java.lang.reflect.Array.newInstance(cls, len);
return (T[])obj;
} catch (Exception e) {
return null;
}
}
}
以上代码中,create 方法实现了动态创建一个泛型类型的实例,createArray 方法实现了动态创建一个泛型类型的实例数组。
先拿一个例子来说明泛型是什么。
有两个类如下,要构造两个类的对象,并打印出各自的成员x。
public class StringFoo {
private String x;
public String getX() {
return x;
}
public void setX(String x) {
this.x = x;
}
}
public class DoubleFoo {
private Double x;
public Double getX() {
return x;
}
public void setX(Double x) {
this.x = x;
}
}
如果要实现对Integer、Long、Date等类型的操作,还要写相应的类,实在是无聊之极。
因此,对上面的两个类进行重构,写成一个类,考虑如下:
上面的类中,成员和方法的逻辑都一样,就是类型不一样。Object是所有类的父类,因此可以考虑用Object做为成员类型,这样就可以实现通用了。
public class ObjectFoo {
private Object x;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
}
调用的代码如下:
public class ObjectFooDemo {
public static void main(String args[]) {
ObjectFoo strFoo = new ObjectFoo();
strFoo.setX("Hello Generics!");
ObjectFoo douFoo = new ObjectFoo();
douFoo.setX(new Double("33"));
ObjectFoo objFoo = new ObjectFoo();
objFoo.setX(new Object());
String str = (String)strFoo.getX();
Double d = (Double)douFoo.getX();
Object obj = objFoo.getX();
System.out.println("strFoo.getX=" + str);
System.out.println("douFoo.getX=" + d);
System.out.println("strFoo.getX=" + obj);
}
}
以上,是没有泛型的情况下,我们编写的代码,采用最顶层基类Object进行类型声明,然后将值传入,取出时要进行强制类型转换。
JDK 从1.5 开始引入了泛型的概念,来优雅解决此类问题。采用泛型技术,编写的代码如下:
public class GenericsFoo <T> {
private T x;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
}
调用的代码如下:
public class GenericsFooDemo {
public static void main(String args[]){
GenericsFoo<String> strFoo=new GenericsFoo<String>();
strFoo.setX("Hello Generics!");
GenericsFoo<Double> douFoo=new GenericsFoo<Double>();
douFoo.setX(new Double("33");
GenericsFoo<Object> objFoo=new GenericsFoo<Object>();
objFoo.setX(new Object());
String str = strFoo.getX();
Double d = douFoo.getX();
Object obj = objFoo.getX();
System.out.println("strFoo.getX=" + str);
System.out.println("douFoo.getX=" + d);
System.out.println("strFoo.getX=" + obj);
}
}
注意,有几点明显的改变:
1. 对象创建时,明确给出类型,如GenericsFoo<String>。
2. 对象通过getX方法取出时,不需要进行类型转换。
3. 对各个方法的调用,如果参数类型与创建时指定的类型不匹配时,编译器就会报错。
那么我们为什么要泛型呢? 有两个好处:
1. 可以在编译时检查存储的数据是否正确。我们开发有一个趋向就是尽早的发现错误,最好就是在编译阶段, 泛型正好符合这一条件。
2. 减少了强制转换, String str = (String)strList.get(0);这样的操作属于一种向下转型, 是比较危险的操作, 当List内存储的对象不适String时就会抛出异常。
JDK1.5 中,java.util 包中的各种数据类型工具类,都支持泛型,在编程中被广泛使用,需要好好掌握。
泛型最常见的应用是应用在类、接口和方法上,下面分别介绍。
3.4.2 泛型应用在接口上:
public interface ValuePair<A,B> {
public A getA();
public B getB();
public String toString();
}
这里A和B都是代表类型。尖角号<>中,可以使用一个类型,也可以使用多个类型。
3.4.3 泛型应用在类上:
public class ValuePairImpl<A,B> {
public final A first;
public final B second;
public ValuePairImpl(A a, B b) { first = a; second = b; }
public A getA() { return first; }
public B getB() { return second; }
public String toString() {
return "(" + first + ", " + second + ")";
}
}
如果这个类实现泛型接口,则相应的写法为:
public class ValuePairImpl<A,B> implements ValuePair<A, B> {
……
}
3.4.4 泛型应用在方法上:
泛型也可以应用在单独的方法上,示例如下:
public class GenericMethod {
public <T> void printValue(T v) {
String str = v.getClass().getName() + “ = “ + v.toString();
System.out.println(str);
}
}
注意语法:在public修饰符后面是<>, 然后是函数返回值, 接着是函数名,函数参数。当然,返回值也可以是泛型的类型。
3.4.5 限制泛型的可用类型
以上介绍的三种泛型应用,应用在接口、类、方法上,是一种通用的做法,对泛型可以传入的类型没有任何限制。但有些场景下,我们希望对可用的类型进行限制,比如希望传入的类型必须从某个类继承(也就是说,必须是某个类的子类、孙类等),这种情况下就用到了泛型限制的语法。
extends:限制泛型类型必须为某个类的后代,包括本类型。
语法:<T extends parentClass>
这里,T为泛型类型,extends 关键字限制泛型类型必须是parentClass的后代。parentClass 指定父类的类型,也可以是接口。
在Java语言中,对类只能单继承,对接口可以多继承,如果要限制指定类型必须从某个类继承,并且实现了多个接口,则语法为:
<T extends parentClass & parentInterface1 & parentInterface2>
注意,类必须在接口前面。
举例如下:
public class BaseClass {
int value;
public BaseClass(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
public class SubClass extends BaseClass{
public SubClass(int value) {
super(value*2);
}
}
public class GenericBound<T extends BaseClass> {
public long sum(List<T> tList) {
long iValue = 0;
for (BaseClass base : tList) {
iValue += base.getValue();
}
return iValue;
}
public static void main(String[] args) {
GenericBound<SubClass> obj = new
GenericBound<SubClass>();
List<SubClass> list = new LinkedList<SubClass>();
list.add(new SubClass(5));
list.add(new SubClass(6));
System.out.println(obj.sum(list));
}
}
运行,输出结果为22.
接着,我们再深入探讨一下。把上面的例子该写如下:
public class GenericBound<T extends BaseClass> {
public long sum(List<T> tList) {
long iValue = 0;
for (BaseClass base : tList) {
iValue += base.getValue();
}
return iValue;
}
public static void main(String[] args) {
// 注意!!!
// 将obj 的类型由GenericBound<SubClass>改为GenericBound<BaseClass>,无法通过编译
GenericBound<BaseClass> obj = new
GenericBound<SubClass>();
List<SubClass> list = new LinkedList<SubClass>();
list.add(new SubClass(5));
list.add(new SubClass(6));
System.out.println(obj.sum(list));
}
}
语句GenericBound<BaseClass> obj = new GenericBound<SubClass>(); 无法通过编译,其根本原因在于,GenericBound类声明处的<T extends BaseClass>,限制了构造此类实例的时候T是确定的一个类型,这个类型是BaseClass的后代。但是BaseClass的后代还又很多,如SubClass3,SubClass4,如果针对每一种都要写出具体的子类类型,那也太麻烦了,干脆还不如用Object通用一下。能不能象普通类那样,用父类的类型引入各种子类的实例,这样不就简单了很多?答案是肯定的,泛型针对这种情况提供了更好的解决方案,那就是“通配符泛型”,下面详细讲解。
3.4.6 通配符泛型
Java 的泛型类型如同 java.lang.String,java.io.File 一样,属于普通的 Java 类型。比方说,下面两个变量的类型就是互不相同的:
Box<Object> boxObj = new Box<Object>();
Box<String> boxStr = new Box<String>();
虽然 String 是 Object 的子类,但是 Box<String> 和 Box<Object> 之间并没有什么关系——Box<String> 不是 Box<Object> 的子类或者子类型,因此,以下赋值语句是非法的:
boxObj = boxStr; // 无法通过编译
因此,我们希望使用泛型时,能象普通类那样,用父类的类型引入各种子类的实例,从而简化程序的开发。Java的泛型中,提供 ? 通配符来满足这个要求。
代码示例如下:
public class WildcardGeneric {
public void print(List<?> lst) {
for (int i = 0; i < lst.size(); i++) {
System.out.println(lst.get(i));
}
}
public static void main(String[] args) {
WildcardGeneric wg = new WildcardGeneric();
ArrayList<String> strList = new ArrayList<String>();
strList.add("One");
strList.add("Two");
wg.print(strList);
LinkedList<Integer> intList = new LinkedList<Integer>();
intList.add(25);
intList.add(30);
wg.print(intList);
}
}
但是这种情况下,WildcardGeneric.print 方法的参数可以接受类型可能对于程序员设计的意图而言太广泛了一点。因为我们可能只是希望 print 可以接受一个List,但这个List中的元素必须是Number的后代。因此,我们要对通配符有所限制,这时可以使用边界通配符(bounded wildcard)形式来满足这个要求。我们将 print 方法再修改一下:
public void print(List<? extends Number> lst) {
for (int i = 0; i < lst.size(); i++) {
System.out.println(lst.get(i));
}
}
这样,List<Integer>、List<Short> 等等类型的变量就可以传给 print 方法,而储存其他类型元素的 List 的泛型类型变量(如List<String>)传给 print 方法将是非法的。
除了 ?extends上边界通配符(upper bounded wildcard)以外,我们还可以使用下边界通配符(lower bounded wildcard),例如 List<? super ViewWindow>。
最后总结一下使用通配符的泛型类型的三种形式:
GenericType<?>
GenericType<? extends upperBoundType>
GenericType<? super lowerBoundType>
3.4.7 泛型深入
我们已经初步掌握了泛型的基本用法,接着再来探讨一下深入的主题。
我们还是先来看一段代码:
public class GenericsFoo <T> {
private T x;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public static void main(String[] args) {
GenericsFoo<String> gf = new GenericsFoo<String>();
gf.setX("Hello");
GenericsFoo<?> gf2 = gf;
gf2.setX("World"); // 报错!!!
String str = gf2.getX(); // 报错!!!
gf2.setX(gf2.getX()); // 报错!!!
}
}
注意,main 方法中的最后三行都是非法的,无法通过编译。本来是一个<String>的泛型,通过<?>来引用后,setX() 传入一个String就报错,getX() 返回值的类型也不是String。更为奇怪的是,语句gf2.setX(gf2.getX()); 就是从里面取出值然后再原封不动设置回去,也不行。这是怎么回事?
为了彻底弄清楚这些问题,我们需要了解JDK对泛型的内部实现原理。先看两个例子:
public class GenericClassTest {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
Class c3 = new ArrayList().getClass();
System.out.println(c1 == c3);
}
}
运行后,输出结果为:
true
true
这个例子说明,泛型ArrayList<String>、ArrayList<Integer>和没有使用泛型的ArrayList 其实是同一个类。就如同没使用泛型一样。
再看第二个例子:
class Element {}
class Box<T> {}
class Pair<KEY, VALUE> {}
public class GenericClassTest2 {
public static void main(String[] args) {
List<Element> list = new ArrayList<Element>();
Map<String,Element> map = new HashMap<String, Element>();
Box<Element> box = new Box<Element>();
Pair<Integer, String> p = new Pair<Integer, String>();
System.out.println(Arrays.toString(
list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
box.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
p.getClass().getTypeParameters()));
}
}
运行后,输出结果为:
[E]
[K, V]
[T]
[KEY, VALUE]
查阅JDK的文档,Class.getTypeParameters() 方法返回一个TypeVariable对象的数组,数组中每个TypeVariable对象描述了泛型中声明的类型。这似乎意味着,我们可以从TypeVariable 对象中找出泛型实例化时真正的类型。但是,从程序运行的输出结果,我们可以看出,Class.getTypeParameters() 返回的一系列TypeVariable 对象,仅仅表征了泛型声明时的参数化类型占位符,真正实例化时的类型都被抛弃了。因此,Java 泛型的真相是:
在泛型代码中,根本就没有参数化类型的信息。
产生这样一个事实的原因在于,JDK 对泛型的内部实现,采用了擦除(erasure)的方式,具体擦除的方式如下:
1) ArrayList<String>、ArrayList<Integer>、ArrayList<?>都被擦除成ArrayList
2) ArrayList<T extends BaseClass>、ArrayList<? extends BaseClass>都被擦除成ArrayList<BaseClass>
3) ArrayList<? super BaseClass>被擦除成ArrayList<BaseClass>
理解了擦除的实现机制后,我们再回过头来,分析一下前面的例子,看看为什么不能通过编译:
public class GenericsFoo <T> {
private T x;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public static void main(String[] args) {
GenericsFoo<String> gf = new GenericsFoo<String>();
gf.setX("Hello");
GenericsFoo<?> gf2 = gf;
gf2.setX("World"); // 报错!!!
String str = gf2.getX(); // 报错!!!
gf2.setX(gf2.getX()); // 报错!!!
}
}
由于擦除机制,GenericsFoo<?>都被擦除成GenericsFoo,类型被丢失后,那么相应的方法声明就变成了:
public Object getX();
public void setX(null x);
因此,泛型类的任何取出值的get方法,返回值都变成了Object,可以被调用,但返回值需要进行类型转换;泛型类的任何设置值的set方法,参数类型都变成了null,任何类型都无法转换成null类型,因此所有的set方法都无法被调用。
这样就形成了一个有趣的现象:对<?>的泛型类型,只能get,不能set。
以上阐述了Java泛型的擦除机制,导致一些有用的类型信息丢失。但我们可以通过一些技巧,让编译器重新构建出类型信息,从而使得set方法可以被正常调用。见如下的代码:
public void setGeneric(GenericsFoo<?> foo) {
setGenericHelper(foo);
}
private<V> void setGenericHelper(GenericsFoo<V> foo) {
foo.setX(foo.getX());
}
setGenericHelper() 是一个泛型方法,泛型方法引入了额外的类型参数(位于返回类型之前的尖括号中),这些参数用于表示参数和/或方法的返回值之间的类型约束。setGenericHelper () 这种声明方式,允许编译器(通过类型接口)对GenericsFoo 泛型的类型参数命名。但一个类型可以有父类、祖父类,还可以实现多个接口,那么编译器会转换成哪个类型呢?
我们用下面这段代码来验证一下:
public class GenericClassTest3 {
public static<T> String getType(T arg) {
return arg.getClass().getName();
}
public static void main(String[] args) {
Integer i = new Integer(5);
System.out.println(GenericClassTest3.getType(i));
}
}
程序运行后,输出结果为:
java.lang.Integer
因此,编译器能够推断 T 是 Integer、Number、 Serializable 或 Object,但它选择 Integer 作为满足约束的最具体类型。
另外,由于泛型的擦除机制,我们也无法直接对泛型类型用new操作符,比如:
public class GenericNew<T> {
public T create() {
T obj = new T(); // 无法通过编译!!!
return obj;
}
}
由于擦除机制,语句T obj = new T(); 擦除后就变成了
obj = new ();
于是就无法通过编译了。
但是,在一些情况下,我们还是需要对泛型类型的动态实例化。对于创建单个对象和创建数组,代码示例如下:
public class GenericNew<T> {
public T create(Class<T> cls) {
try {
Object obj = cls.newInstance();
return (T)obj;
} catch(Exception e) {
return null;
}
}
public T[] createArray(Class<T> cls, int len) {
try {
Object obj = java.lang.reflect.Array.newInstance(cls, len);
return (T[])obj;
} catch (Exception e) {
return null;
}
}
}
以上代码中,create 方法实现了动态创建一个泛型类型的实例,createArray 方法实现了动态创建一个泛型类型的实例数组。
评论
4 楼
bestree007
2011-08-26
很好,就是方法返回类型之前的<T>还是模糊
3 楼
J_Love_me
2010-11-24
很好,很强大。
2 楼
Javakeith
2010-10-04
很好!解释很细致!
1 楼
q821424508
2010-04-18
经典啊 应用之 还是一楼啊
发表评论
-
关掉myeclips中maven的自动更新
2011-05-14 09:06 1372(1)关掉maven自动更新: window-prefere ... -
配置访问远程服务器中jconsole的参数
2011-01-05 15:06 1464在linux中 catalina.sh中添加如下命令 JAV ... -
JDBC连接数据库
2009-05-19 11:17 1373数据库连接池,获得connection 1. import j ... -
什么是堆栈
2009-05-18 17:09 1461什么是堆栈 在计算机领 ... -
java优化
2009-05-18 13:54 08.不要重复初始化变量 ■默认情况下,调用 ... -
Spring 动态设置数据源
2009-05-18 13:47 4239Spring2.0.1以后的版本已经支持配置多数据源,并且可以 ...
相关推荐
Java 第二阶段提升编程能力【泛型】---- 代码 Java 第二阶段提升编程能力【泛型】---- 代码 Java 第二阶段提升编程能力【泛型】---- 代码 Java 第二阶段提升编程能力【泛型】---- 代码 Java 第二阶段提升编程能力...
标题与描述均提到了“全面总结Java泛型--实例”,这表明文章旨在深入解析Java泛型的概念,并通过具体示例来展示其应用。Java泛型是Java编程语言的一个强大特性,它允许在编译时检查类型安全,并且所有的强制转换都是...
java-泛型-面试题.docx
Java 泛型是Java编程语言中的一个重要特性,它在2004年随着...这个视频教程"29-API-集合框架-泛型-使用"应该会深入浅出地讲解这些概念,对于想要提升Java编程技能的初学者或有经验的开发者来说,都是很好的学习资源。
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是...
Java 泛型是JDK 1.5引入的一项重要特性,极大地增强了代码的类型安全性和重用性。泛型允许我们在编程时指定容器(如集合)所存储的对象类型,从而在编译时期就能捕获类型错误,避免了运行时的ClassCastException。 ...
包含11个Word文档,包含了C#的集合、字符串和正则表达式、泛型、内存管理和指针、反射、线程和同步、.NET的安全性、COM的互相操作性、文件和注册表操作、使用GDI+画图、Windows Presentation Foundation。...
泛型是Java语言的一个重要特性,首次出现在Java SE 1.5版本中。它的引入主要是为了解决在集合操作中类型安全性的问题,并通过引入参数化类型的概念,提高了代码的复用性与可读性。 ### 泛型概念 泛型,即参数化...
补充知识:泛型1---马克-to-win java视频的详细描述与介绍
**Delphi泛型库——DGL详解** 在Delphi编程环境中,DGL(The Delphi Generic Library)是一个非常重要的工具,它为开发者提供了类型安全、高效且易用的泛型容器和算法。这个库的设计灵感来源于C++的STL(Standard ...
【Java基础】泛型方法 - 右撇子 - 博客频道 - CSDN.NET
- **STL简介**:STL(Standard Template Library,标准模板库)是C++标准库的一个重要组成部分,它提供了一系列高效的数据结构和算法实现。STL的核心包括容器、迭代器、算法、函数对象以及适配器等几个部分。 - **...
补充知识2 ---马克-to-win java视频泛型的详细描述与介绍
在这个“TypeScript泛型类 - 把类作为参数类型的泛型类”示例中,我们将深入探讨如何利用泛型在TypeScript中创建灵活的、可复用的类模板。 泛型是TypeScript中的一个核心概念,它允许我们在编写代码时定义和重用...
本demo是自己写的一个命令模式的demo,设计到了一般的命令模式,命令模式的变种,万能命令,block封装命令,复合型命令等等,对整个demo我写了一篇文章:https://www.jianshu.com/p/266794b9eda6 ,有兴趣的可以对照着文章...
数学和泛型编程-高效编程的奥秘(英文版pdf)原名:From_Mathematics_to_Generic_Programming,作者;Alexander A. Stepanov Daniel E. Rose
day02【Collection、泛型】-笔记.md
本文将深入探讨泛型类、泛型方法、泛型接口和泛型委托,并通过实例来阐述它们的应用。 首先,我们来看泛型类。泛型类是具有一个或多个类型参数的类。类型参数是在定义类时使用的占位符,实际的类型在创建类的实例时...
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是...