转自:http://www.blogjava.net/killme2008/archive/2007/06/05/122174.html
泛型引入java语言已经有很长一段时间了,在JDK5出来的时候也非常认真地学习过,不过学习的资料都是网上泛滥并且重复的教程。这几天下了《The Java Programming Language》的第4版,准备把jdk5引入的新东西再重新系统地学习一次,同时再次回顾下java基础。今天记录下学习泛型那一章的注意点。
一、泛型类型的声明
1.需要着重注意的一点,比如声明类Cell<E>:
然后如此使用:
那么Cell<String>和Cell<Integer>是两个类吗?不,他们是同一个类,通过下面的实验证明:
java泛型的实现采用的“擦拭法”,Cell<E>仍然是一个类,无论E被任何具体的类型所替代。
2.泛型的类型参数不能用于static变量、static方法和static初始化,比如下面的使用方式都不能编译通过:
3.类型参数可以继承其他的类和接口,如果有多个接口可以用&符号连接,通过extend参数限制了类型参数的范围,比如:
4.比较有趣的内部类的泛型,对于静态内部类的类型参数可以与外部类的类型参数名不一样,静态内部类的类型参数与外部类的类型参数其实没有一点关系,比如:
Cell<E>类的声明和SingleLinkQueue<E> 两个类中的E仅仅是名称相同,他们之间的关联是通过head和tail的声明才关联在一起,你可以将Cell<E>中的E改成F也没关系,比如:
而一般的内部类就不一样了,内部类可以直接使用外部类的类型参数甚至隐藏。
二、子类型与通配符
今天读了第2节,泛型的使用比我原先所知的更为复杂,java语法本来以简洁优美著称,随着java5,java7的到来,语法是越来越复杂,甚至可以说丑陋!-_-
要知道一点,比如List<Integer>不是List<Number>的子类,而是Collection<Integer>的子类。因为List<Integer>和List<Number>的类型是一样的,都是List。那么如何表示参数化类型是Number的子类呢?这就需要用到通配符:
而通配符List<?>等价于:
通配符只能用于变量、局部变量、参数类型和返回类型,不能用于命名类和接口。比如下面的代码将不能编译通过:
三、泛型方法和类型推断
如果我们想参数化方法的参数和返回值的类型,这就引出了泛型方法的声明,声明一个泛型方法的方式如下:
这个方法限制传入的参数的类型与返回的参数类型将一致,可以看到,在方法签名前加上<T>即可。我们可以这样调用这个方法:
这样的调用是不是比较奇怪?幸好提供了 类型推断,根据参数的类型来自动判断方法的类型(比如返回值类型),因此可以直接调用:
如果方法有两个类型变量,类型推断将怎么处理呢?比如:
然后我们传入两个参数,一个String,一个int,那么返回什么呢?
泛型引入java语言已经有很长一段时间了,在JDK5出来的时候也非常认真地学习过,不过学习的资料都是网上泛滥并且重复的教程。这几天下了《The Java Programming Language》的第4版,准备把jdk5引入的新东西再重新系统地学习一次,同时再次回顾下java基础。今天记录下学习泛型那一章的注意点。
一、泛型类型的声明
1.需要着重注意的一点,比如声明类Cell<E>:
package
net.rubyeye.javaprogramming.generic;
public class Cell < E > {
private Cell < E > next;
private E element;
public Cell(E element) {
this .element = element;
}
public Cell(E element, Cell < E > next) {
this .next = next;
this .element = element;
}
public E getElement() {
return element;
}
public void setElement(E element) {
this .element = element;
}
public Cell < E > getNext() {
return next;
}
public void setNext(Cell < E > next) {
this .next = next;
}
}
public class Cell < E > {
private Cell < E > next;
private E element;
public Cell(E element) {
this .element = element;
}
public Cell(E element, Cell < E > next) {
this .next = next;
this .element = element;
}
public E getElement() {
return element;
}
public void setElement(E element) {
this .element = element;
}
public Cell < E > getNext() {
return next;
}
public void setNext(Cell < E > next) {
this .next = next;
}
}
然后如此使用:
Cell
<
String
>
strCell
=
new
Cell
<
String
>
(
"
Hello
"
);
Cell < Integer > intCell = new Cell < Integer > ( 25 );
Cell < Integer > intCell = new Cell < Integer > ( 25 );
那么Cell<String>和Cell<Integer>是两个类吗?不,他们是同一个类,通过下面的实验证明:
assertTrue(strCell.getClass()
==
intCell.getClass()));
java泛型的实现采用的“擦拭法”,Cell<E>仍然是一个类,无论E被任何具体的类型所替代。
2.泛型的类型参数不能用于static变量、static方法和static初始化,比如下面的使用方式都不能编译通过:
public
class
Cell
<
E
>
{
private static Cell < E > next;
private static void test(E e){
}
同样,静态方法是与类相关联的,调用也只能通过类,假设Cell有一个静态方法test,怎么调用才是正确的呢?
private static Cell < E > next;
private static void test(E e){
}
Cell
<
E
>
.test();
//
编译错误
Cell < String > .test(); // 同样编译错误
Cell.test(); // 正确的方式
类似的,泛型的类型参数不能用于声明数组类型,比如下面的代码同样无法编译通过:
Cell < String > .test(); // 同样编译错误
Cell.test(); // 正确的方式
class
SingleLinkQueue
<
E
>
{
//
public E[] toArray() {
//
}
}
//
public E[] toArray() {
//
}
}
3.类型参数可以继承其他的类和接口,如果有多个接口可以用&符号连接,通过extend参数限制了类型参数的范围,比如:
interface
SortedCharSeqCollection
<
E
extends
Comparable
<
E
>
& CharSequence > {
// sorted char sequence collection methods
}
SortedCharSeqCollection的类型参数E强制继承自Comparable和CharSequence接口,也就是替代的具体的类型参数必须实现这两个接口,从而限制了类型参数(type parameter)。
& CharSequence > {
// sorted char sequence collection methods
}
4.比较有趣的内部类的泛型,对于静态内部类的类型参数可以与外部类的类型参数名不一样,静态内部类的类型参数与外部类的类型参数其实没有一点关系,比如:
class
SingleLinkQueue
<
E
>
{
static class Cell < E > {
private Cell < E > next;
private E element;
public Cell(E element) {
this .element = element;
}
public Cell(E element, Cell < E > next) {
this .element = element;
this .next = next;
}
public E getElement() {
return element;
}
/* rest of Cell methods as before */
}
protected Cell < E > head;
protected Cell < E > tail;
/* rest of SingleLinkQueue methods as before */
}
static class Cell < E > {
private Cell < E > next;
private E element;
public Cell(E element) {
this .element = element;
}
public Cell(E element, Cell < E > next) {
this .element = element;
this .next = next;
}
public E getElement() {
return element;
}
/* rest of Cell methods as before */
}
protected Cell < E > head;
protected Cell < E > tail;
/* rest of SingleLinkQueue methods as before */
}
Cell<E>类的声明和SingleLinkQueue<E> 两个类中的E仅仅是名称相同,他们之间的关联是通过head和tail的声明才关联在一起,你可以将Cell<E>中的E改成F也没关系,比如:
package
net.rubyeye.javaprogramming.generic;
class AnotherSingleLinkQueue < E > {
static class Cell < F > {
private Cell < F > next;
private F element;
public Cell(F element) {
this .element = element;
}
public Cell(F element, Cell < F > next) {
this .element = element;
this .next = next;
}
public F getElement() {
return element;
}
/* rest of Cell methods as before */
}
protected Cell < E > head;
protected Cell < E > tail;
/* rest of SingleLinkQueue methods as before */
}
class AnotherSingleLinkQueue < E > {
static class Cell < F > {
private Cell < F > next;
private F element;
public Cell(F element) {
this .element = element;
}
public Cell(F element, Cell < F > next) {
this .element = element;
this .next = next;
}
public F getElement() {
return element;
}
/* rest of Cell methods as before */
}
protected Cell < E > head;
protected Cell < E > tail;
/* rest of SingleLinkQueue methods as before */
}
而一般的内部类就不一样了,内部类可以直接使用外部类的类型参数甚至隐藏。
二、子类型与通配符
今天读了第2节,泛型的使用比我原先所知的更为复杂,java语法本来以简洁优美著称,随着java5,java7的到来,语法是越来越复杂,甚至可以说丑陋!-_-
要知道一点,比如List<Integer>不是List<Number>的子类,而是Collection<Integer>的子类。因为List<Integer>和List<Number>的类型是一样的,都是List。那么如何表示参数化类型是Number的子类呢?这就需要用到通配符:
List
<?
extends
Number
>
表示类型变量是Number或者Number的子类。这个就是所谓的上界通配符,同样,如果要表示类型变量是Number或者Number的super type,可以使用下界通配符:
List
<?
super
Number
>
而通配符List<?>等价于:
List
<?
extends
Object
>
通配符只能用于变量、局部变量、参数类型和返回类型,不能用于命名类和接口。比如下面的代码将不能编译通过:
class
MyList
implements
List
<?>
{
//
}
通配符有另一个问题:因为通配符代表的是未知的类型,你不能在任何需要类型信息的地方使用它。比如下面的代码同样无法编译通过:
//
}
SingleLinkQueue
<?>
strings
=
new SingleLinkQueue < String > ();
strings.add( " Hello " ); // INVALID: 无法编译
SingleLinkQueue <? extends Number > numbers =
new SingleLinkQueue < Number > ();
numbers.add(Integer.valueOf( 25 )); // INVALID: 无法编译
new SingleLinkQueue < String > ();
strings.add( " Hello " ); // INVALID: 无法编译
SingleLinkQueue <? extends Number > numbers =
new SingleLinkQueue < Number > ();
numbers.add(Integer.valueOf( 25 )); // INVALID: 无法编译
三、泛型方法和类型推断
如果我们想参数化方法的参数和返回值的类型,这就引出了泛型方法的声明,声明一个泛型方法的方式如下:
<
T
>
T passThrough(T obj) {
return obj;
}
return obj;
}
这个方法限制传入的参数的类型与返回的参数类型将一致,可以看到,在方法签名前加上<T>即可。我们可以这样调用这个方法:
String s1
=
"
Hello
"
;
String s2 = this . < String > passThrough(s1);
String s2 = this . < String > passThrough(s1);
这样的调用是不是比较奇怪?幸好提供了 类型推断,根据参数的类型来自动判断方法的类型(比如返回值类型),因此可以直接调用:
String s1
=
"
Hello
"
;
String s2 = this .passThrough(s1);
String s2 = this .passThrough(s1);
如果方法有两个类型变量,类型推断将怎么处理呢?比如:
<
T
>
T passThrough(T obj1,T obj2) {
return (T)(obj1.toString() + obj2.toString());
}
return (T)(obj1.toString() + obj2.toString());
}
然后我们传入两个参数,一个String,一个int,那么返回什么呢?
String s1
=
"
test
"
;
String s3 = this .passThrough(s1, 1 ); // 编译出错
类型推断是比较复杂的,这里将返回的将是Object类型,是传入的参数类型的
交集
String s3 = this .passThrough(s1, 1 ); // 编译出错