下面是自己学习Java的generic programming后的一点总结,以供大家尽快地对其有一个大概的了解。
文章的思路是:1.
定义;2.
原理;3.
通配符(wildcard types)。
1. 定义
什么是generic programming?在定义
class,interface和method的时候,generics可以把class或者是interface当成一个类型参数。参考的是http://docs.oracle.com/javase/tutorial/java/generics/why.html
首先看怎么定义一个generic class,举个代码
例子,这样更直观一点。
public class Pair<T>
{
public Pair() { first = null; second = null; }
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
private T first;
private T second;
public static void main(String[] args){
Pair<Integer> integerPair = new Pair(0, 1);
Pair<String> stringPair = new Pair("thinking in java", "java core");
...
}
}
然后看下怎么定义generic method,还是看代码。
class ArrayAlg
{
public static T getMiddle(T[] a)
{
return a[a.length / 2];
}
}
到此,定义类和方法的具体写法就介绍完了,下面也很容易想到对于参数类型“T”有没有什么
限制呢?下面就介绍“T”的使用限制范围(Bounds of Type Variable)。
可以定义代码:
public static <T extends Comparable> Pair minmax(T[] a)...
其中Comparable就是T的bounding type,实际就是T的一个继承关系中的限制条件了。
注意,这个概念很重要,在之后介绍generics在虚拟机中怎么运行的时候会提及。
2. 原理
也就是介绍generics在虚拟机中是
什么样的。
在虚拟机中,所有的类或者是
接口都必须有一个类型,那“T”的类型是什么呢?答案自然不是“T”,而是在定义了generic type之后,虚拟机会自动提供一个raw type。
那什么又是raw type?他不是一个具体的类或者是接口,而是定义generics的所有的T的bounding type的总称,按语法来讲,他不是个名词,而是个代词。
比如最开始的例子,Pair<T>,其的raw type就是Object,而...<T extends Comparable>...的raw type就是Comparable。
在虚拟机中,第一个Pair<T>例子的代码就会变成:
public class Pair
{
public Pair(Object first, Object second)
{
this.first = first;
this.second = second;
}
public Object getFirst() { return first; }
public Object getSecond() { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
private Object first;
private Object second;
...
}
那经过转化之后,“T”已经不是原来的那个“T”了,但是为什么Pair<Integer>(当然Pair<String>同理)还是原来的Pair<Integer>呢?
因为编译器会在把值取出来之前再强制转化类型。
即,generics在虚拟机里的过程是:
- 存,存成bounding type类型的
- 取,把bounding type类型的东西再还原回来
这个过程非常重要,很多地方都是拿这个过程解释的,当然,也有一部分冲突是由此引起的。
下面就介绍一个由于此过程引起的冲突以及由此引出的一个概念,bridge method。
看如下代码,注意这段代码是合法的:
class DateInterval extends Pair<Date>
{
public void setSecond(Date second)
{
if (second.compareTo(getFirst()) >= 0)
super.setSecond(second);
}
. . .
}
因为DateInterval是继承Pair<Date>,自然继承了Pair<Date>的setSecond方法,但是由于在虚拟机中Pair<Date>的setSecond方法被存为
public void setSecond(Object second)
{
...
}
即相当于在DateInterval中有两个名字相同,参数不同(但是注意参数有继承关系)的方法,如果要调用setSecond就不知所措了,下图阴影部分为继承Pair<Date>所得,仅为示例之用。
这时候编译器会提供一个bridge method,来解决两个方法冲突的问题。
public void setSecond(Object second) { setSecond((Date) second); }
这样问题就得到解决了,注意,这里是编译器的事儿,与
写代码无关。
在原理部分需要注意的几点是
- 实际上是没有generics存在的,也就是上面的“T”
- 所有的type parameters都会换成他们的bounding type
- bridge method会解决冲突的问题
- 在虚拟机里面会执行类型转换以确保安全
3. 通配符(wildcard types)
也就是在使用generics的时候嫌麻烦,可以使得generic type中的type parameter可以在继承关系上使用更灵活点,或者是可以使用type parameter的父类,或者是可以使用其的子类。
比如在方法:
public static void printBuddies(Pair<Employee> p)
{
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName() + " are buddies.";
}
其中要想给printBuddies传递一个Pair<Manager>类型的参数是不合法的,于是乎产生了一种新的定义方法:
public static void printBuddies(Pair<? extends Employee> p)
这样,就可以把Pair<Manager>类型的参数传入方法了。同理还可以定义代码:
public static void printBuddies(Pair<? super Manager> p)
这样的意思就变成所有Manager的父类都可以调用此段代码了。
最后也可以就通配符“?”一个,什么继承关系都没有,比如:
public static void printBuddies(Pair<?> p)
具体通配符的用法可以参考http://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html。
没有介绍的太详细,但求大概能对generics有一个整体印象。
参考资料
- http://docs.oracle.com/javase/tutorial/extra/generics/index.html
- Core.Java.Volume.I.Fundamentals,8th.Edition
- 描述: DateInterval
- 大小: 9.4 KB