Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限。
2、简单泛型
促成泛型出现最引人注目的一个原因就是为了创造容器类。
首先看一个只能持有单个对象的类,这个类可以明确指定其持有的对象的类型
上面的类的可重用性不怎么样,无法持有其他类型的任何对象,下面通过持有Object类型的对象实现
通常而言,我们只会用容器来存储一种类型的队形,泛型的主要目的之一就是用来指定容器要持有什么类型的对象,由编译器来保证类型的正确性:
{
h3 =
(
也就是告诉编译器想使用什么类型,然后编译器帮你处理一切细节。
2.1、一个一元组类库
为了在一次方法调用返回多个对象,可以使用元组的概念:将一组对象直接打包存储于其中的一个单一对象,这个类容器允许读取其中元素,但是不允许向其中存放新的对象(也称为数据传送对象,或信使)。
元组可以具有任意长度,元组中的对象可以使任意不同类型的,下面的程序是一个二维元组,能够持有两个对象:
{
<span class="javadoc">/**
- 使用继承机制实现长度更长的元组
*/
<span class="class"><span class="keyword">class <span class="title">ThreeTuple<<span class="title">A,<span class="title">B,<span class="title">C> <span class="keyword">extends <span class="title">TwoTuple<<span class="title">A,<span class="title">B> {
<span class="indent"> <span class="keyword">public <span class="keyword">final C third;
<span class="indent"> <span class="keyword">public ThreeTuple(A a,B b,C c) {
<span class="indent"> <span class="indent"> <span class="keyword">super(a,b);
<span class="indent"> <span class="indent"> third = c;
<span class="indent"> }
<span class="indent"> <span class="keyword">public String toString() {
<span class="indent"> <span class="indent"> <span class="keyword">return <span class="string">"(" + first + <span class="string">"," + second + <span class="string">"," + third +<span class="string">")";
<span class="indent"> }
}
<span class="class"><span class="keyword">class <span class="title">FourTuple<<span class="title">A,<span class="title">C,<span class="title">D> <span class="keyword">extends <span class="title">ThreeTuple<<span class="title">A,<span class="title">C> {
<span class="indent"> <span class="keyword">public <span class="keyword">final D fourth;
<span class="indent"> <span class="keyword">public FourTuple(A a,C c,D d) {
<span class="indent"> <span class="keyword">super(a,b,c);
<span class="indent"> fourth = d;
<span class="indent"> }
<span class="indent"> <span class="keyword">public String toString() {
<span class="indent"> <span class="keyword">return <span class="string">"(" + first + <span class="string">"," +
<span class="indent"> <span class="indent"> third + <span class="string">"," + fourth + <span class="string">")";
<span class="indent"> }
}
为了使用元组,只需定义长度合适的元组,作为方法的返回值就可以了
f() {
}
<span class="class"><span class="keyword">class <span class="title">FiveTuple<<span class="title">A,<span class="title">D,<span class="title">E> <span class="keyword">extends <span class="title">FourTuple<<span class="title">A,<span class="title">D> {
<span class="keyword">public <span class="keyword">final E fifth;
<span class="keyword">public FiveTuple(A a,D d,E e) {
<span class="keyword">super(a,c,d);
fifth = e;
}
<span class="keyword">public String toString() {
<span class="keyword">return <span class="string">"(" + first + <span class="string">"," +
third + <span class="string">"," + fourth + <span class="string">"," + fifth + <span class="string">")";
}
}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_2_1 {
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String args){
TupleTest.test();
}
}
2.2、一个堆栈类
{
{
next;
next) {
top = ();
2.3、RandomList
持有特定类型对象的列表,每次调用其上的select()方法,可以随机地取一个元素:
{
storage = ();
rs = ();
3、泛型接口
泛型接口也可以应用于接口,例如生成器
是工厂模式的一种应用,不过使用生成器创建新的对象的时候,不需要任何的参数,而工厂方法一般需要参数。
下面我就来创建一个生成器来展示泛型在接口中的使用场景
{ T next(); }
下面的类是Generator接口的另一个实现,负责生成Fibonacci数列:
{
下面编写一个实现了Iterable的Fibonacci生成器,通过继承来创建适配器类:
{
<span class="annotation">@Override
<span class="keyword">public Iterator iterator() {
<span class="keyword">return <span class="keyword">new Iterator() {
<span class="keyword">public <span class="keyword">boolean hasNext() { <span class="keyword">return n > <span class="number">0; }
<span class="keyword">public Integer next() {
n--;
<span class="keyword">return IterableFibonacci.<span class="keyword">this.next();
}
<span class="keyword">public <span class="keyword">void remove() { <span class="comment">// Not implemented
<span class="keyword">throw <span class="keyword">new UnsupportedOperationException();
}
};
}
<span class="keyword">public <span class="keyword">static <span class="keyword">void test(String[] args) {
<span class="keyword">for(<span class="keyword">int i : <span class="keyword">new IterableFibonacci(<span class="number">18))
System.out.print(i + <span class="string">" ");
}
} <span class="comment">/ Output:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
/<span class="comment">//:~
4、泛型方法
4.1、杠杆利用类型参数推断
首先是一个静态方法:
Map map(){
();
<span class="indent"> // 然后可以这样创建一个Map:
<span class="indent"> public static void test(String[] args){
<span class="indent"> <span class="indent"> Map<span class="tag"><<span class="title">String,<span class="attribute">List<<span class="attribute">Cat>> catMap = New.map();
<span class="indent"> }
}
可以发现,右边的不用再按照以前的这种写法了:
Map catMap = new HashMap();
左边声明部分的类型为右边提供了一种推断,使得编译器可以直接创造出来具体的类了。不过,这种场景没有声明,直接使用New.map()是编译不通过的,因为没有像这里左边的可以推断的依据了, 如下面的,加入f()是一个方法,需要传入一个Map,如下的写法是编译不通过的:
如果确实还是想按照上面那样使用,则可以考虑使用显示类型说明了,在操作符与方法名直接插入尖括号显示的指明类型,代码如下:
不过这种方式很少使用。也就是说,在编写非赋值语句时,才要这样的说明,而使用不了杠杆利用类型推断。
我们为了方便,可以使用同样的方式创建其他的容器了,可惜JDK本身没有提供这样的类:
Map map() {
return new HashMap();
}
public static List list() {
return new ArrayList();
}
public static LinkedList lList() {
return new LinkedList();
}
public static Set set() {
return new HashSet();
}
public static Queue queue() {
return new LinkedList();
}
// Examples:
public static void test(String[] args) {
Map> sls = New.map();
List ls = New.list();
LinkedList lls = New.lList();
Set ss = New.set();
Queue qs = New.queue();
}
}
4.2、可变参数与泛型方法
可变参数也是可以使用泛型声明类型的:
<span class="indent"> public static <span class="tag"><<span class="title">T> List<span class="tag"><<span class="title">T> makeList(T... args){
<span class="indent"> <span class="indent"> List<span class="tag"><<span class="title">T> result = new ArrayList<span class="tag"><<span class="title">T>();
<span class="indent"> <span class="indent"> for(T item : args){
<span class="indent"> <span class="indent"> <span class="indent"> result.add(item);
<span class="indent"> <span class="indent"> }
<span class="indent"> <span class="indent"> return result;
<span class="indent"> }
<span class="indent"> public static void test(String[] args){
<span class="indent"> <span class="indent"> List<span class="tag"><<span class="title">String> ls = makeList("Jay","Mike");
<span class="indent"> }
}
4.3、用于Generator的泛型方法
通过使用泛型方法,封装更加抽象的方法,比如下面的fill(),然后在使用的时候才传入需要使用的的具体对象:
<span class="indent"> <span class="keyword">public <span class="keyword">static Collection fill(
<span class="indent"> <span class="indent"> <span class="indent"> Collection coll,Generator gen,<span class="keyword">int n){
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i=<span class="number">0; i<n; i++){
<span class="indent"> <span class="indent"> <span class="indent"> coll.add(gen.next());
<span class="indent"> <span class="indent"> }
<span class="indent"> <span class="indent"> <span class="keyword">return coll;
<span class="indent"> }
}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_4_3 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args){
<span class="indent"> <span class="indent"> Collection shapes = GenericGenerator.fill(<span class="keyword">new ArrayList(),<span class="keyword">new ShapeGenerator(),<span class="number">2);
<span class="indent"> <span class="indent"> <span class="keyword">for(Shape a : shapes){
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(a);
<span class="indent"> <span class="indent"> }
<span class="indent"> }
}
4.4、一个通用的Generator
通过使用泛型类,我们更创建一个更加通用的生成器Generator。
{
<span class="indent"> <span class="keyword">private Class type;
<span class="indent"> <span class="keyword">public BasicGenerator(Class type){
<span class="indent"> <span class="indent"> <span class="keyword">this.type = type;
<span class="indent"> }
<span class="indent"> <span class="annotation">@Override
<span class="indent"> <span class="keyword">public T next() {
<span class="indent"> <span class="indent"> <span class="keyword">try {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">return type.newInstance();
<span class="indent"> <span class="indent"> } <span class="keyword">catch (Exception e) {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">throw <span class="keyword">new RuntimeException(e);
<span class="indent"> <span class="indent"> }
<span class="indent"> }
<span class="indent"> <span class="keyword">public <span class="keyword">static Generator create(Class type){
<span class="indent"> <span class="indent"> <span class="keyword">return <span class="keyword">new BasicGenerator(type);
<span class="indent"> }
}
由于使用了newInstance()方法,所以这里生产的类必须要提供一个默认的无参构造函数。
下面试验一下,创建一个对象,为了标示是新创建的对象,在类里面保存一个static的计数器,每创建一个对象就加1:
<span class="indent"> <span class="keyword">private <span class="keyword">static <span class="keyword">long counter = <span class="number">0;
<span class="indent"> <span class="keyword">private <span class="keyword">final <span class="keyword">long id = counter++;
<span class="indent"> <span class="keyword">public <span class="keyword">long id(){
<span class="indent"> <span class="indent"> <span class="keyword">return id;
<span class="indent"> }
<span class="indent"> <span class="keyword">public String toString(){
<span class="indent"> <span class="indent"> <span class="keyword">return <span class="string">"countObject" + id;
<span class="indent"> }
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void test(String[] args){
<span class="indent"> <span class="indent"> Generator gen = BasicGenerator.create(CountObject.class);
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i=<span class="number">0; i<<span class="number">5; i++){
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(gen.next());
<span class="indent"> <span class="indent"> }
<span class="indent"> }
}
<span class="comment">/
test 输入结果如下:
countObject0
countObject1
countObject2
countObject3
countObject4
/
4.5、简化元组的使用
我们可以发现之前创建的元组,在使用的时候都传入了一长串具体的类型,通过杠杆利用类型推断参数,我们其实可以直接省略掉那一长串具体的类型了,添加一个static方法,可以使该方法成为更通用的类库的方法了:
<span class="indent"> <span class="keyword">public <span class="keyword">static<A,B,C> ThreeTuple<A,C> tuple(A a,C c){
<span class="indent"> <span class="indent"> <span class="keyword">return <span class="keyword">new ThreeTuple<A,C>(a,c);
<span class="indent"> }
}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_4_5 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args){
<span class="indent"> <span class="indent"> <span class="comment">// 根据左边的类型自动判断右边的类型,无需手动创建时指明类型了
<span class="indent"> <span class="indent"> ThreeTuple<Cat,String> tt = TupleTest2.tuple(<span class="keyword">new Cat(),<span class="number">1,<span class="string">"Jason");
<span class="indent"> <span class="indent"> System.out.println(tt);
<span class="indent"> }
}
4.6、一个Set实用工具
<span class="keyword">class WatercolorSets {
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> Set set1 =
<span class="indent"> <span class="indent"> EnumSet.range(Watercolors.BRILLIANT_RED,Watercolors.VIRIDIAN_HUE);
<span class="indent"> Set set2 =
<span class="indent"> <span class="indent"> EnumSet.range(Watercolors.CERULEAN_BLUE_HUE,Watercolors.BURNT_UMBER);
<span class="indent"> System.<span class="keyword">out.println(<span class="string">"set1: " + set1);
<span class="indent"> System.<span class="keyword">out.println(<span class="string">"set2: " + set2);
<span class="indent"> System.<span class="keyword">out.println(<span class="string">"union(set1,set2): " + union(set1,set2));
<span class="indent"> Set subset = intersection(set1,set2);
<span class="indent"> System.<span class="keyword">out.println(<span class="string">"intersection(set1,set2): " + subset);
<span class="indent"> System.<span class="keyword">out.println(<span class="string">"difference(set1,subset): " +
<span class="indent"> <span class="indent"> difference(set1,subset));
<span class="indent"> System.<span class="keyword">out.println(<span class="string">"difference(set2,subset): " +
<span class="indent"> <span class="indent"> difference(set2,subset));
<span class="indent"> System.<span class="keyword">out.println(<span class="string">"complement(set1,set2): " +
<span class="indent"> <span class="indent"> complement(set1,set2));
}
} <span class="comment">/ Output: (Sample)
set1: [BRILLIANT_RED,VIRIDIAN_HUE]
set2: [CERULEAN_BLUE_HUE,BURNT_UMBER]
union(set1,set2): [SAP_GREEN,VIRIDIAN_HUE]
intersection(set1,set2): [ULTRAMARINE,VIRIDIAN_HUE]
difference(set1,subset): [ROSE_MADDER,BRILLIANT_RED]
difference(set2,subset): [RAW_UMBER,BURNT_UMBER]
complement(set1,MAGENTA]
/<span class="comment">//:~
下面的示例使用Sets.difference() 打印出 java.util包中各种Collection类与Map类之间的方法差异:
methodSet(Class> type) {
Set result = ();
type) {
System. result = ();
c : type.getInterfaces())
result.add(c.getSimpleName());
System. superset,Class> subset) {
System. comp = Sets.difference(
methodSet(superset),methodSet(subset));
comp.removeAll(
5、匿名内部类
泛型方法还可以应用于内部类和匿名内部类,下面是使用匿名内部类实现Generator接口的例子:
generator() {
() {
<span class="class"><span class="keyword">class <span class="title">Teller {
<span class="keyword">private <span class="keyword">static <span class="keyword">long counter = <span class="number">1;
<span class="keyword">private <span class="keyword">final <span class="keyword">long id = counter++;
<span class="keyword">private Teller() {}
<span class="keyword">public String toString() { <span class="keyword">return <span class="string">"Teller " + id; }
<span class="comment">// A single Generator object:
<span class="keyword">public <span class="keyword">static Generator generator =
<span class="keyword">new Generator() {
<span class="keyword">public Teller next() { <span class="keyword">return <span class="keyword">new Teller(); }
};
}
<span class="class"><span class="keyword">class <span class="title">BankTeller {
<span class="keyword">public <span class="keyword">static <span class="keyword">void serve(Teller t,Customer c) {
System.out.println(t + <span class="string">" serves " + c);
}
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
Random rand = <span class="keyword">new Random(<span class="number">47);
Queue line = <span class="keyword">new LinkedList();
Generators.fill(line,Customer.generator(),<span class="number">15);
List tellers = <span class="keyword">new ArrayList();
Generators.fill(tellers,Teller.generator,<span class="number">4);
<span class="keyword">for(Customer c : line)
serve(tellers.get(rand.nextInt(tellers.size())),c);
}
} <span class="comment">/ Output:
Teller 3 serves Customer 1
Teller 2 serves Customer 2
Teller 3 serves Customer 3
Teller 1 serves Customer 4
Teller 1 serves Customer 5
Teller 3 serves Customer 6
Teller 1 serves Customer 7
Teller 2 serves Customer 8
Teller 3 serves Customer 9
Teller 3 serves Customer 10
Teller 2 serves Customer 11
Teller 4 serves Customer 12
Teller 2 serves Customer 13
Teller 1 serves Customer 14
Teller 1 serves Customer 15
/<span class="comment">//:~
6、构建复杂模型
泛型的一个重要好处是能够简单而安全地创建复杂的模型,例如很容易的创建List元组:
> {
tl =
();
i: tl)
下面一个实例展示使用泛型类型来构建复杂模型是多么简单的事情
generator =
() {
<span class="class"><span class="keyword">class <span class="title">Shelf <span class="keyword">extends <span class="title">ArrayList<<span class="title">Product> {
<span class="keyword">public Shelf(<span class="keyword">int nProducts) {
Generators.fill(<span class="keyword">this,Product.generator,nProducts);
}
}
<span class="class"><span class="keyword">class <span class="title">Aisle <span class="keyword">extends <span class="title">ArrayList<<span class="title">Shelf> {
<span class="keyword">public Aisle(<span class="keyword">int nShelves,<span class="keyword">int nProducts) {
<span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < nShelves; i++)
add(<span class="keyword">new Shelf(nProducts));
}
}
<span class="class"><span class="keyword">class <span class="title">CheckoutStand {}
<span class="class"><span class="keyword">class <span class="title">Office {}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Store <span class="keyword">extends <span class="title">ArrayList<<span class="title">Aisle> {
<span class="keyword">private ArrayList checkouts =
<span class="keyword">new ArrayList();
<span class="keyword">private Office office = <span class="keyword">new Office();
<span class="keyword">public Store(<span class="keyword">int nAisles,<span class="keyword">int nShelves,<span class="keyword">int nProducts) {
<span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < nAisles; i++)
add(<span class="keyword">new Aisle(nShelves,nProducts));
}
<span class="keyword">public String toString() {
StringBuilder result = <span class="keyword">new StringBuilder();
<span class="keyword">for(Aisle a : <span class="keyword">this)
<span class="keyword">for(Shelf s : a)
<span class="keyword">for(Product p : s) {
result.append(p);
result.append(<span class="string">"n");
}
<span class="keyword">return result.toString();
}
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
System.out.println(<span class="keyword">new Store(<span class="number">14,<span class="number">5,<span class="number">10));
}
} <span class="comment">/ Output:
258: Test,price: $400.99
861: Test,price: $160.99
868: Test,price: $417.99
207: Test,price: $268.99
551: Test,price: $114.99
278: Test,price: $804.99
520: Test,price: $554.99
140: Test,price: $530.99
...
/<span class="comment">//:~
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_6 {
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args){
TupleList.main(args);
}
}
7、擦除的神秘之处
看个奇怪的问题,考虑下面输出的结果:
().getClass();
Class c2 = new ArrayList().getClass();
System.out.println(c1 == c2);
输出的结果竟然是true。
下面我们用Class.getTypeParameters()方法返回TypeVariable对象数组看个究竟:
我们发现输出结果为:
这里只是参数的占位符,所以,在泛型代码内部,无法获得任何有关泛型参数类型的信息。你可以知道诸如类型参数标示符和泛型类型边界这类信息,但却无法知道用来创建某个特定实例的实际的类型参数。Java中的泛型是使用擦除来实现的,所以在使用泛型的时候,任何具体的类型信息都被擦除了,只知道当前使用的是一个对象。所以上面才会出现相等的情况。
7.1、C++的方式
查看下面的一段C++的泛型代码:
<span class="keyword">template<<span class="keyword">class T> <span class="keyword">class Manipulator {
T obj;
<span class="keyword">public:
Manipulator(T x) { obj = x; }
<span class="keyword">void manipulate() { obj.f(); }
};
<span class="keyword">class HasF {
<span class="keyword">public:
<span class="keyword">void f() { <span class="built_in">cout << <span class="string">"HasF::f()" << endl; }
};
<span class="keyword">int main() {
HasF hf;
Manipulator manipulator(hf);
manipulator.manipulate();
} <span class="comment">/* Output:
HasF::f()
C++编写的泛型,当模板被实例化时,模板代码知道其模板参数的类型,C++编译器将进行检查,如果泛型对象调用了一个当前实例化对象不存在的方法,则报一个编译期错误。例如上面的manipulate里面调用了obj.f(),因为实例化的HasF存在这个方法,所以不会报错。
而Java是使用擦除实现泛型的,在没有指定边界的情况下,是不能在泛型类里面直接调用泛型对象的方法的,如下面的例子:
{
<span class="indent"> <span class="keyword">private T obj;
<span class="indent"> <span class="keyword">public Manipulator(T x){
<span class="indent"> <span class="indent"> obj = x;
<span class="indent"> }
<span class="indent"> <span class="keyword">public <span class="keyword">void doSomething(){
<span class="indent"> <span class="indent"> obj.f(); <span class="comment">// 编译错误
<span class="indent"> }
}
通过没有边界的obj调用f(),编译出错了,下面指定边界,让其通过编译:
{
<span class="indent"> <span class="keyword">private T obj;
<span class="indent"> <span class="keyword">public Manipulator(T x){
<span class="indent"> <span class="indent"> obj = x;
<span class="indent"> }
<span class="indent"> <span class="keyword">public <span class="keyword">void doSomething(){
<span class="indent"> <span class="indent"> obj.f(); <span class="comment">// 编译错误
<span class="indent"> }
}
<span class="class"><span class="keyword">class <span class="title">HasF{
<span class="indent"> <span class="keyword">public <span class="keyword">void f(){
<span class="indent"> <span class="indent"> System.out.println(<span class="string">"HasF.f();");
<span class="indent"> }
}
上面的例子,把泛型类型参数擦除到了HasF,就好像在类的声明中用HasF替换了T一样。
7.2、迁移兼容性
泛型是JDK1.5才出现的,所以为了兼容,采用了擦除的方式实现。泛型类型只有在静态类型检查期间才出现,在此之后,程序中所有泛型类型都被擦除,替换为他们的非泛型上界。例如List将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除为Object。
7.3、擦除的问题
擦除使得现有的非泛型客户端代码能够在不改变的情况下继续使用,直至客户端准备好用泛型重写这些代码。
但是擦除的代价也是显著的,泛型不能用于显式的引用运行时类型的操作中,如转型,instanceof和new操作符,因为所有关于参数的类型信息都丢失了。无论何时当你在编写泛型代码时,必须时刻提醒自己,你只是看起来好像拥有有关参数的类型信息而已,实际上,它只是一个Object。
当要使用@SuppressWarnings("unchecked") 关闭警告时,最好尽量地“聚焦”,这样就不会因为过于宽泛地关闭警告,而导致意外的屏蔽掉真正的问题。
下面的Derived3的错误意味着编译器期望得到一个原生基类,当你希望将参数类型不要仅仅当做Object处理时,需要付出额外努力来管理边界。
{
<span class="class"><span class="keyword">class <span class="title">Derived1<<span class="title">T> <span class="keyword">extends <span class="title">GenericBase<<span class="title">T> {}
<span class="class"><span class="keyword">class <span class="title">Derived2 <span class="keyword">extends <span class="title">GenericBase {} <span class="comment">// No warning
<span class="comment">// class Derived3 extends GenericBase<?> {}
<span class="comment">// Strange error:
<span class="comment">// unexpected type found : ?
<span class="comment">// required: class or interface without bounds
<span class="class"><span class="keyword">class <span class="title">ErasureAndInheritance {
<span class="indent"> <span class="annotation">@SuppressWarnings(<span class="string">"unchecked")
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> Derived2 d2 = <span class="keyword">new Derived2();
<span class="indent"> <span class="indent"> Object obj = d2.get();
<span class="indent"> <span class="indent"> d2.set(obj); <span class="comment">// Warning here!
<span class="indent"> }
} <span class="comment">///:~
7.4、边界处的动作
{
holder =
();
上面的代码的set()方法会在编译期接受检查,而get()的时候直接取出了String类型,其实此处还是会进行转型的,只不过是由编译器自动插入的相当于插入了这样的代码:(String)holder.get(),详细的转型处理可以编译成字节码查看。
8、擦除的补偿
正因为类型信息被擦除了,所以和类型相关的代码都无法工作了,如下的:
{
上面的instanceof方法也没法使用了额,为了在泛型类中能够判断类型,可以引入类型标签:
{
<span class="indent"> Class<span class="tag"><<span class="title">T> kind;
<span class="indent"> public ClassTypeCapture(Class<span class="tag"><<span class="title">T> kind){
<span class="indent"> <span class="indent"> this.kind = kind;
<span class="indent"> }
<span class="indent"> public boolean f(Object arg){
<span class="indent"> <span class="indent"> return kind.isInstance(arg);
<span class="indent"> }
<span class="indent"> public static void main(String[] args){
<span class="indent"> <span class="indent"> ClassTypeCapture<span class="tag"><<span class="title">String> ctc = new ClassTypeCapture<span class="tag"><<span class="title">String>(String.class);
<span class="indent"> <span class="indent"> System.out.println(ctc.f("art")); // true
<span class="indent"> <span class="indent"> System.out.println(ctc.f(1)); // false
<span class="indent"> }
}
8.1、创建类型实例
我们怎么在一个泛型类中创建泛型的对象呢,上面直接创建的方法也是编译不通过的?我们可以使用泛型工厂的方式。可以保存一个类型标签,使用Class.newInstance()的方式,创建泛型的对象, 但是这种情况,传入的类型标签对应的类必须要有构造函数,所以不推荐这样干,下面说说显示的工厂这种方法(限制其类型,使得只能接受实现了这个工厂的类):
首先来创建一个工厂接口:
{
T create();
}
接下来创建一个对象,里面包含了一个需要使用工厂创建的泛型对象:
{
private T x;
public > Foo(F factory){
x = factory.create();
}
}
接下来创建显示的工厂:
{
{
这样子我们就可以创建泛型类中的泛型对象了,通过传入上面的显示工厂:
(( <span class="comment">// TODO 模板方法设计模式
}
}
8.2、泛型数组
从上面Erased的例子中可以看出,不能直接创建泛型数组,一般使用ArrayList替代。
{
();
<span class="class"><span class="keyword">class <span class="title">Generic<<span class="title">T> {}
但是可以按照编译器喜欢的方式来定义一个引用,却永远都不能创建这个确切类型的数组。
[] gia;
}
不能创建这个确切类型的数组
[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
gia = (Generic[])new Object[SIZE]; // 编译通过,运行报ClassCastException错误,因为数组将跟踪它们的实际类型,而这个类型是在数组被创建时确定的。
// Runtime type is the raw (erased) type:
gia = new Generic[SIZE]; // 不能这样创建,Cannot create a generic array of Generic
gia = (Generic[])new Generic[SIZE]; // 成功创建泛型数组的唯一方法就是创建一个被擦除类型的新数组,然后对其转型。
System.out.println(gia.getClass().getSimpleName()); // Generic[]
gia[0] = new Generic();
gia[1] = new Object(); // 错误:cannot convert from Object to Generic
gia[2] = new Generic(); // 错误:cannot convert from Generic to Generic
}
}
下面是一个泛型数组包装器
{
gai =
(
因为有了擦除,数组的运行时类型就只能是Object[],如果我们立即将其转型为T[],在编译期该数组的实际类型就会丢失,而编译器可能会错过某些潜在的错误检查。正因为这样,最好是在集合内部使用Object[],当使用数组元素时,添加一个对T的类型转换
{
gai =
(
可以传递一个类型标记,使得rep()方法可以工作:
{
type, gai =
(
9、边界
使用无界泛型调用的方法只能是Object可以调用的方法,如果能够将参数类型限定为某个类型子集,就可以用这些类型子集来调用方法了。
使用extends关键字给泛型声明添加边界:
{
{
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_9 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args){
<span class="indent"> <span class="indent"> GoldenFish fish = <span class="keyword">new GoldenFish();
<span class="indent"> <span class="indent"> <span class="comment">// 创建泛型类,super关键字对应的类继承结构
<span class="indent"> <span class="indent"> Item1 item1 = <span class="keyword">new Item1(fish);
<span class="indent"> <span class="indent"> item1.doSomething();
<span class="indent"> }
}
10、通配符
泛型参数表达式中的问号。
首先来看一个例子,可以向导出类型Apple的数组,赋予基类型的数组引用:
<span class="class"><span class="keyword">class <span class="title">CovariantArrays {
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
Fruit[] fruit = <span class="keyword">new Apple[<span class="number">10];
fruit[<span class="number">0] = <span class="keyword">new Apple(); <span class="comment">// OK
fruit[<span class="number">1] = <span class="keyword">new Jonathan(); <span class="comment">// OK
<span class="comment">// Runtime type is Apple[],not Fruit[] or Orange[]:
<span class="keyword">try {
<span class="comment">// Compiler allows you to add Fruit:
<span class="comment">// 运行时抛出异常,此时的数组机制知道它处理的是Apple[]
fruit[<span class="number">0] = <span class="keyword">new Fruit(); <span class="comment">// ArrayStoreException
} <span class="keyword">catch(Exception e) { System.out.println(e); }
<span class="keyword">try {
<span class="comment">// Compiler allows you to add Oranges:
fruit[<span class="number">0] = <span class="keyword">new Orange(); <span class="comment">// ArrayStoreException
} <span class="keyword">catch(Exception e) { System.out.println(e); }
}
} <span class="comment">/ Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
/<span class="comment">//:~
我们使用泛型来替代数组,使得错误可以再编译期可以检测到:
flist = new ArrayList();
}
泛型是不会自动向上转型的,不能把一个涉及Apple的泛型赋给一个涉及Fruit的泛型。
有时候你想要在两个类型之间建立某种类型的向上转型关系,这正是通配符所允许的:
:具有任何从Fruit继承的类型的列表,但是为了向上转型为flist,这个类型是什么并没有人关心
flist = (); 也不可以成功添加
10.1、编译器有多聪明
使用了 ? extends Fruit 的泛型的方法参数,将不能传入任何具体的参数。
,编译器不能了解这里需要Fruit的哪个具体子类型,因此不会接受任何类型的Fruit,
flist =
为了在类型中使用了通配符的情况系禁止contains的这类调用,我们需要在参数列表中使用类型参数
{
Fruit = Apple; // Cannot upcast
fruit = Apple;
10.2、逆变
超类型通配符:可以声明通配符是由某个特定类的rene积累来界定的 super MyClass>,也可以使用类型参数 super T>这使得你可以安全的传递一个类型对象到泛型类型中,因此,有了超类型通配符,就可以向Collection写入了:
apples) {
apples.add(new Apple());
apples.add(new Jonathan());
// apples.add(new Fruit()); // Error
}
}
根据如何能够向一个泛型类型“写入”(传递给一个方法),以及如何能够从一个泛型类型中“读取”(从一个方法中返回),来着手思考子类型和超类型边界?
超类型边界放松了在可以向方法传递的参数上所作的限制
void writeExact(List list,T item) {
apples = new ArrayList();
fruit = new ArrayList();
void writeWithWildcard(List list,T item) {
下面继续看一个关于协变和通配符的例子
T readExact( fruit = Arrays.asList( {
fruitReader = ();
) cannot be
{
fruitReader =
();
10.3、无边界通配符
无界通配符>看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。
编译器初看起来是支持这种判断的:
list2;
list3;
和 extends Object>是不同的
在这些情况中,>可以被认为是一种装饰,但它仍旧很有价值,声明了“我是想用Java的泛型来编写这段代码,我在这里并不是要用原生类型但在这种情况下,泛型参数可以持有任何类型。”
下面展示无界通配符的一个重要应用:当处理多个泛型参数时有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型的这种能力会显得很重要:
map2;
map3;
map) { map2 = map; }
map) { map3 = map; }
());
());
());
List
- List表示持有任何Object类型的原生List
- List>表示具有某种特定类型的非原生List,只是我们不知道那种类型是什么
编译器什么时候才会关注原生类型和涉及无界通配符的类型之间的差异呢?
下面用例子演示下:
<span class="indent"> <span class="comment">// Can't do this; don't have any 'T':
<span class="indent"> <span class="comment">// T t = holder.get();
<span class="indent"> <span class="comment">// OK,but type information has been lost:
<span class="indent"> Object obj = holder.<span class="keyword">get();
}
<span class="comment">// Similar to rawArgs(),but errors instead of warnings:
<span class="comment">// 这里演示了<?>和原生类型是不同的:
<span class="keyword">static <span class="keyword">void unboundedArg(Holder<?> holder,Object arg) {
<span class="indent"> <span class="comment">// 原生Holder将持有任何类型的组合,而Holder<?>将持有具有某种具体类型的同构集合,因此不能只是向其中传递Object
<span class="indent"> holder.<span class="keyword">set(arg); <span class="comment">// Error:
<span class="indent"> <span class="comment">// set(capture of ?) in Holder<capture of ?>
<span class="indent"> <span class="comment">// cannot be applied to (Object)
<span class="indent"> <span class="comment">// holder.set(new Wildcards()); // Same error
<span class="indent"> <span class="comment">// Can't do this; don't have any 'T':
<span class="indent"> <span class="comment">// T t = holder.get();
<span class="indent"> <span class="comment">// OK,but type information has been lost:
<span class="indent"> Object obj = holder.<span class="keyword">get();
}
<span class="keyword">static T exact1(Holder holder) {
<span class="indent"> T t = holder.<span class="keyword">get();
<span class="indent"> <span class="keyword">return t;
}
<span class="keyword">static T exact2(Holder holder,T arg) {
<span class="indent"> holder.<span class="keyword">set(arg);
<span class="indent"> T t = holder.<span class="keyword">get();
<span class="indent"> <span class="keyword">return t;
}
<span class="comment">// 在Holder类型上的限制被放松为包括持有任何扩展自T的对象的Holder,
<span class="comment">// 传入了Holder之后,为了防止将Orange放置到Holder,<span class="comment">// 对set的调用都是不允许的,但是你仍旧知道任何来自Holder<? extends Fruit的对象至少是Fruit,因此get()是允许的
<span class="keyword">static T wildSubtype(Holder<? extends T> holder,T arg) {
<span class="indent"> <span class="comment">// holder.set(arg); // Error:
<span class="indent"> <span class="comment">// set(capture of ? extends T) in
<span class="indent"> <span class="comment">// Holder<capture of ? extends T>
<span class="indent"> <span class="comment">// cannot be applied to (T)
<span class="indent"> T t = holder.<span class="keyword">get();
<span class="indent"> <span class="keyword">return t;
}
<span class="comment">// 展示超类型通配
<span class="keyword">static <span class="keyword">void wildSupertype(Holder<? super T> holder,T arg) {
<span class="indent"> <span class="comment">// holder可以是持有任何T的基类型的容器,因此,set()可以接受T,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是T)
<span class="indent"> holder.<span class="keyword">set(arg);
<span class="indent"> <span class="comment">// T t = holder.get(); // Error: 由holder持有的类型可以是任何超类型,因此唯一安全的类型就是Object
<span class="indent"> <span class="comment">// Incompatible types: found Object,required T
<span class="indent"> <span class="comment">// OK,but type information has been lost:
<span class="indent"> Object obj = holder.<span class="keyword">get();
}
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> Holder raw = <span class="keyword">new Holder();
<span class="indent"> <span class="comment">// Or:
<span class="indent"> raw = <span class="keyword">new Holder();
<span class="indent"> Holder qualified = <span class="keyword">new Holder();
<span class="indent"> Holder<?> unbounded = <span class="keyword">new Holder();
<span class="indent"> Holder<? extends Long> bounded = <span class="keyword">new Holder();
<span class="indent"> Long lng = <span class="number">1L;
<span class="indent"> rawArgs(raw,lng);
<span class="indent"> rawArgs(qualified,lng);
<span class="indent"> rawArgs(unbounded,lng);
<span class="indent"> rawArgs(bounded,lng);
<span class="indent"> unboundedArg(raw,lng);
<span class="indent"> unboundedArg(qualified,lng);
<span class="indent"> unboundedArg(unbounded,lng);
<span class="indent"> unboundedArg(bounded,lng);
<span class="indent"> <span class="comment">// Object r1 = exact1(raw); // Warnings:
<span class="indent"> <span class="comment">// Unchecked conversion from Holder to Holder
<span class="indent"> <span class="comment">// Unchecked method invocation: exact1(Holder)
<span class="indent"> <span class="comment">// is applied to (Holder)
<span class="indent"> Long r2 = exact1(qualified);
<span class="indent"> Object r3 = exact1(unbounded); <span class="comment">// Must return Object
<span class="indent"> Long r4 = exact1(bounded);
<span class="indent"> <span class="comment">// Long r5 = exact2(raw,lng); // Warnings:
<span class="indent"> <span class="comment">// Unchecked conversion from Holder to Holder
<span class="indent"> <span class="comment">// Unchecked method invocation: exact2(Holder,T)
<span class="indent"> <span class="comment">// is applied to (Holder,Long)
<span class="indent"> Long r6 = exact2(qualified,lng);
<span class="indent"> <span class="comment">// Long r7 = exact2(unbounded,lng); // Error:
<span class="indent"> <span class="comment">// exact2(Holder,T) cannot be applied to
<span class="indent"> <span class="comment">// (Holder<capture of ?>,Long)
<span class="indent"> <span class="comment">// Long r8 = exact2(bounded,T) cannot be applied
<span class="indent"> <span class="comment">// to (Holder<capture of ? extends Long>,Long)
<span class="indent"> <span class="comment">// Long r9 = wildSubtype(raw,lng); // Warnings:
<span class="indent"> <span class="comment">// Unchecked conversion from Holder
<span class="indent"> <span class="comment">// to Holder<? extends Long>
<span class="indent"> <span class="comment">// Unchecked method invocation:
<span class="indent"> <span class="comment">// wildSubtype(Holder<? extends T>,T) is
<span class="indent"> <span class="comment">// applied to (Holder,Long)
<span class="indent"> Long r10 = wildSubtype(qualified,lng);
<span class="indent"> <span class="comment">// OK,but can only return Object:
<span class="indent"> Object r11 = wildSubtype(unbounded,lng);
<span class="indent"> Long r12 = wildSubtype(bounded,lng);
<span class="indent"> <span class="comment">// wildSupertype(raw,lng); // Warnings:
<span class="indent"> <span class="comment">// Unchecked conversion from Holder
<span class="indent"> <span class="comment">// to Holder<? super Long>
<span class="indent"> <span class="comment">// Unchecked method invocation:
<span class="indent"> <span class="comment">// wildSupertype(Holder<? super T>,Long)
<span class="indent"> wildSupertype(qualified,lng);
<span class="indent"> <span class="comment">// wildSupertype(unbounded,lng); // Error:
<span class="indent"> <span class="comment">// wildSupertype(Holder<? super T>,T) cannot be
<span class="indent"> <span class="comment">// applied to (Holder<capture of ?>,Long)
<span class="indent"> <span class="comment">// wildSupertype(bounded,T) cannot be
<span class="indent"> <span class="comment">// applied to (Holder<capture of ? extends Long>,Long)
}
}
exact2()具有最多的限制,因为它希望精确地得到一个Holder,以及一个具有类型T的参数,正是由此,它将产生错误或警告,除非提供确切的参数。又是这样很好,但是如果它过于受限,那么就可以使用通配符,这取决于是否想要从泛型参数中返回类型确定的返回值(wildSubtype())或者是想要向泛型参数传递类型确定的参数(wildSupertype())
使用确切类型来替代通配符的好处是可以用泛型参数来做更多的事,但是使用通配符使得你必须接受范围更宽的参数化类型作为参数。因此,必须逐个情况地权衡利弊,找到更适合你的需求的方法。
10.4、捕获转换
下面演示一下捕获转换
holder) {
holder) {
(,still figures it out:
wildcarded = (
捕获转换非常有趣,但是非常受限:捕获转换在这个情况下才会工作,即在方法内部,需要使用确切的类型的时候,注意,不能从f2()中返回T,因为T对于f2()来说是未知的。
11 问题
11.1、任何基本类型都不能作为类型参数
可以使用基本类型的包装类,使用容器的时候,自动包装机制会把基本类型转换为包装类,但是记住:自动包装无法用于数组,所以泛型数组不能传入基本类型的数组。
<span class="indent"> <span class="comment">// 使用 t 填充数组
<span class="indent"> <span class="keyword">public <span class="keyword">static T[] fill(T[] a,T t){
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i=<span class="number">0; i<a.length; i++){
<span class="indent"> <span class="indent"> <span class="indent"> a[i] = t;
<span class="indent"> <span class="indent"> }
<span class="indent"> <span class="indent"> <span class="keyword">return a;
<span class="indent"> }
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args){
<span class="indent"> <span class="indent"> fill(<span class="keyword">new Integer[<span class="number">10],<span class="number">3);
<span class="indent"> <span class="indent"> <span class="comment">// fill(new int[10],3); //编译失败,因为自动包装机制不能应用于数组,因此这无法工作。
<span class="indent"> }
}
11.2、实现参数化接口
一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。
{}
<span class="class"><span class="keyword">class <span class="title">Employee <span class="keyword">implements <span class="title">Payable<<span class="title">Employee>{}
<span class="javadoc">/**
- 下面不能编译通过,因为擦除将会将 Payable 和 Payable 简化为相同的类Payable。
- 去掉泛型,却可以通过编译。
-
*/
<span class="class"><span class="keyword">class <span class="title">Hourly <span class="keyword">extends <span class="title">Employee <span class="keyword">implements <span class="title">Payable<<span class="title">Hourly>{}
11.3、转型和警告
使用带有泛型类型参数的转型或indtanceof不会有任何效果
{
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_11_3 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">final <span class="keyword">int SIZE = <span class="number">10;
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> FixedSizeStack strings =
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new FixedSizeStack(SIZE);
<span class="indent"> <span class="indent"> <span class="keyword">for(String s : <span class="string">"A B C D E F G H I J".split(<span class="string">" "))
<span class="indent"> <span class="indent"> <span class="indent"> strings.push(s);
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < SIZE; i++) {
<span class="indent"> <span class="indent"> <span class="indent"> String s = strings.pop();
<span class="indent"> <span class="indent"> <span class="indent"> System.out.print(s + <span class="string">" ");
<span class="indent"> <span class="indent"> }
<span class="indent"> }
<span class="indent"> <span class="annotation">@SuppressWarnings(<span class="string">"unchecked")
<span class="indent"> <span class="keyword">public <span class="keyword">void f(String filepath) <span class="keyword">throws Exception{
<span class="indent"> <span class="indent"> <span class="comment">// 下面演示由readObject()方法读取转型
<span class="indent"> <span class="indent"> ObjectInputStream in = <span class="keyword">new ObjectInputStream(<span class="keyword">new FileInputStream(filepath));
<span class="indent"> <span class="indent"> <span class="comment">// 如果没有压制的注解,则会阐释警告 Unchecked cast from Object to List
<span class="indent"> <span class="indent"> <span class="comment">// List circles = (List)in.readObject();
<span class="indent"> <span class="indent"> <span class="comment">// 如果想继续使用泛型的情况下不产生警告,则可以使用Java EE5中的使用泛型类来转型
<span class="indent"> <span class="indent"> List circles = List.class.cast(in.readObject());
<span class="indent"> <span class="indent"> <span class="comment">// 但是你继续添加如下转型是仍会得到一个警告
<span class="indent"> <span class="indent"> <span class="comment">// Type safety: Unchecked cast from List to List
<span class="indent"> <span class="indent"> circles = (List)List.class.cast(in.readObject());
<span class="indent"> }
}
11.4、重载
由于擦除的原因,重载方法将产生相同的类型签名
{
// 错误:Method f(List) has the same erasure f(List) as another method in type UseList
void f(List v) {}
void f(List v) {}
}
11.5、基类劫持了接口
{
and Comparable
12 自限定的类型
12.1、古怪的循环泛型
下面演示一个循环泛型的例子
{}
下面演示一下循环泛型的作用
首先创建一个泛型类
{
实现循环泛型类,作用:基类BasicHolder用导出类Subtype替代其方法间传递的参数。
{}
使用
12.2、自限定
首先看一个没有自限定的例子,BasicHolder可以使用任何类型作为其泛型参数:
<span class="class"><span class="keyword">class <span class="title">BasicOther <span class="keyword">extends <span class="title">BasicHolder<<span class="title">Other> {}
<span class="class"><span class="keyword">class <span class="title">Unconstrained {
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
BasicOther b = <span class="keyword">new BasicOther(),b2 = <span class="keyword">new BasicOther();
b.set(<span class="keyword">new Other());
Other other = b.get();
b.f();
}
}
我们使用自限定类型其实就是为了要求在继承关系中,像下面这样使用这个类
class A extends SelfBounded{}
这会强制要求将正在定义的子类当做参数传递给基类
下面看一个自限定类型的例子
> {
set(T arg) {
<span class="class"><span class="keyword">class <span class="title">A <span class="keyword">extends <span class="title">SelfBounded<<span class="title">A> {}
<span class="class"><span class="keyword">class <span class="title">B <span class="keyword">extends <span class="title">SelfBounded<<span class="title">A> {} <span class="comment">// Also OK
<span class="class"><span class="keyword">class <span class="title">C <span class="keyword">extends <span class="title">SelfBounded<<span class="title">C> {
<span class="indent"> C setAndGet(C arg) { set(arg); <span class="keyword">return get(); }
}
<span class="class"><span class="keyword">class <span class="title">D {}
<span class="comment">// Can't do this:
<span class="comment">// class E extends SelfBounded {}
<span class="comment">// Compile error: Type parameter D is not within its bound
<span class="comment">// Alas,you can do this,so you can't force the idiom:
<span class="class"><span class="keyword">class <span class="title">F <span class="keyword">extends <span class="title">SelfBounded {}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_12_2 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> A a = <span class="keyword">new A();
<span class="indent"> <span class="indent"> <span class="comment">// 直接使用SelfBounded,传入类似A这样的子类
<span class="indent"> <span class="indent"> SelfBounded bounded = <span class="keyword">new SelfBounded();
<span class="indent"> <span class="indent"> a.set(<span class="keyword">new A());
<span class="indent"> <span class="indent"> a = a.set(<span class="keyword">new A()).get();
<span class="indent"> <span class="indent"> a = a.get();
<span class="indent"> <span class="indent"> C c = <span class="keyword">new C();
<span class="indent"> <span class="indent"> c = c.setAndGet(<span class="keyword">new C());
<span class="indent"> }
}
还可以将自限定用于泛型方法
> T f(T arg) {
12.3、参数协变
前一节演示的自限定类型的价值在于可以产生协变参数类型:方法参数类型会随子类而变化。
而自限定类型还可以产生于子类类型相同的返回值,如前一节的B类,但这并不是重要的,因为协变返回类型是在JavaSE5中引入的,之前的JDK版本并不能编译自限定
<span class="class"><span class="keyword">interface <span class="title">OrdinaryGetter {
Base get();
}
子接口继承OrdinaryGetter,其中的get()方法返回值为到处的雷系Derived,这在早先的Java版本是不合法的
<span class="class"><span class="keyword">class <span class="title">CovariantReturnTypes {
<span class="keyword">void test(DerivedGetter d) {
Derived d2 = d.get();
}
}
下面演示一下自限定类型中导出类的方法接受导出类型而不是及类型为参数的
首先看一下非泛型代码中,参数不能随子类型发生变化的例子
<span class="class"><span class="keyword">class <span class="title">DerivedSetter <span class="keyword">extends <span class="title">OrdinarySetter {
<span class="keyword">void set(Derived derived) {
System.out.println(<span class="string">"DerivedSetter.set(Derived)");
}
}
<span class="class"><span class="keyword">class <span class="title">OrdinaryArguments {
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
Base base = <span class="keyword">new Base();
Derived derived = <span class="keyword">new Derived();
DerivedSetter ds = <span class="keyword">new DerivedSetter();
ds.set(derived);
ds.set(base); <span class="comment">// 这里ds实际上有两个方法,在继承的时候,set方法被重载了,而不是覆盖了。
}
}
而在自限定中,是这样的:编译器不能识别将基类型当做参数传递给set()的尝试,因为没有任何方法具有这样的签名。
实际上,这个参数已经被覆盖了:
> {
<span class="class"><span class="keyword">interface <span class="title">Setter <span class="keyword">extends <span class="title">SelfBoundSetter<<span class="title">Setter> {}
<span class="class"><span class="keyword">class <span class="title">SelfBoundingAndCovariantArguments {
<span class="indent"> <span class="keyword">void testA(Setter s1,Setter s2,SelfBoundSetter sbs) {
<span class="indent"> <span class="indent"> s1.set(s2);
<span class="indent"> <span class="indent"> <span class="comment">// s1.set(sbs); // 错误,不存在这样的方法,这个方法已被Setter子类覆盖
<span class="indent"> <span class="indent"> <span class="comment">// set(Setter) in SelfBoundSetter
<span class="indent"> <span class="indent"> <span class="comment">// cannot be applied to (SelfBoundSetter)
<span class="indent"> }
}
而没有使用自限定类型的情况下,普通的基础机制就会介入,这个时候方法就会被重载,就像在非泛型的情况下一样:
{
13、动态类型安全
没有使用泛型之前的代码,是不能限定放入容器的元素类型的,所以旧式的代码有可能会破坏你的容器。
JavaSE5中有一组静态方法可以检查类型问题:checkedCollection(),checkedList(),checkedMap(),checkedSet(),checkedSortedMap(),checkedSortedSet()
下面演示一下这种情况:
<span class="indent"> <span class="comment">// 假设oldStyleMethod()是遗留的代码
<span class="indent"> <span class="annotation">@SuppressWarnings(<span class="string">"unchecked")
<span class="indent"> <span class="keyword">static <span class="keyword">void oldStyleMethod(List probablyDogs) {
<span class="indent"> <span class="indent"> probablyDogs.add(<span class="keyword">new Cat());
<span class="indent"> }
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> <span class="comment">// 没有检验之前插入是没有问题的
<span class="indent"> <span class="indent"> List dogs1 = <span class="keyword">new ArrayList();
<span class="indent"> <span class="indent"> oldStyleMethod(dogs1); <span class="comment">// Quietly accepts a Cat
<span class="indent"> <span class="indent"> <span class="comment">// 检验之后抛出 ClassCastException
<span class="indent"> <span class="indent"> List dogs2 = Collections.checkedList(
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new ArrayList(),Dog.class);
<span class="indent"> <span class="indent"> <span class="keyword">try {
<span class="indent"> <span class="indent"> <span class="indent"> oldStyleMethod(dogs2); <span class="comment">// Throws an exception
<span class="indent"> <span class="indent"> } <span class="keyword">catch(Exception e) {
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(e);
<span class="indent"> <span class="indent"> }
<span class="indent"> <span class="indent"> <span class="comment">// Derived types work fine:
<span class="indent"> <span class="indent"> List pets = Collections.checkedList(
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new ArrayList(),Pet.class);
<span class="indent"> <span class="indent"> pets.add(<span class="keyword">new Dog());
<span class="indent"> <span class="indent"> pets.add(<span class="keyword">new Cat());
<span class="indent"> }
}
14、异常
泛型使用于异常是非常受限的,catch语句不能捕获泛型类型的异常,因为在编译器和运行时都必须知道异常的确切类型,泛型类也不能直接或间接的继承自Throwable(这将进一步阻止你去定义不能捕获的泛型异常),但是,类型参数可能会在一个方法的throws子句中用到,这使得你可以编写随检查型异常的类而发生变化的泛型代码:
{
resultCollector)
ProcessRunner 是一个可变数组,保存Processor
> {
processAll() resultCollector = ();
processor : <span class="class"><span class="keyword">class <span class="title">Failure1 <span class="keyword">extends <span class="title">Exception {}
<span class="class"><span class="keyword">class <span class="title">Processor1 <span class="keyword">implements <span class="title">Processor<<span class="title">String,<span class="title">Failure1> {
<span class="indent"> <span class="keyword">static <span class="keyword">int count = <span class="number">3;
<span class="indent"> <span class="keyword">public <span class="keyword">void
<span class="indent"> process(List resultCollector) <span class="keyword">throws Failure1 {
<span class="indent"> <span class="indent"> <span class="keyword">if(count-- > <span class="number">1)
<span class="indent"> <span class="indent"> <span class="indent"> resultCollector.add(<span class="string">"Hep!");
<span class="indent"> <span class="indent"> <span class="keyword">else
<span class="indent"> <span class="indent"> <span class="indent"> resultCollector.add(<span class="string">"Ho!");
<span class="indent"> <span class="indent"> <span class="keyword">if(count < <span class="number">0)
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">throw <span class="keyword">new Failure1();
<span class="indent"> <span class="indent"> }
<span class="indent"> }
<span class="class"><span class="keyword">class <span class="title">Failure2 <span class="keyword">extends <span class="title">Exception {}
<span class="class"><span class="keyword">class <span class="title">Processor2 <span class="keyword">implements <span class="title">Processor<<span class="title">Integer,<span class="title">Failure2> {
<span class="indent"> <span class="keyword">static <span class="keyword">int count = <span class="number">2;
<span class="indent"> <span class="keyword">public <span class="keyword">void
<span class="indent"> process(List resultCollector) <span class="keyword">throws Failure2 {
<span class="indent"> <span class="indent"> <span class="keyword">if(count-- == <span class="number">0)
<span class="indent"> <span class="indent"> <span class="indent"> resultCollector.add(<span class="number">47);
<span class="indent"> <span class="indent"> <span class="keyword">else {
<span class="indent"> <span class="indent"> <span class="indent"> resultCollector.add(<span class="number">11);
<span class="indent"> <span class="indent"> }
<span class="indent"> <span class="indent"> <span class="keyword">if(count < <span class="number">0)
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">throw <span class="keyword">new Failure2();
<span class="indent"> }
}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_14 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> ProcessRunner<String,Failure1> runner =
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new ProcessRunner<String,Failure1>();
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < <span class="number">3; i++)
<span class="indent"> <span class="indent"> <span class="indent"> runner.add(<span class="keyword">new Processor1());
<span class="indent"> <span class="indent"> <span class="keyword">try {
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(runner.processAll()); <span class="comment">// [Hep!,Hep!,Ho!]
<span class="indent"> <span class="indent"> } <span class="keyword">catch(Failure1 e) {
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(e);
<span class="indent"> <span class="indent"> }
<span class="indent"> <span class="indent"> ProcessRunner<Integer,Failure2> runner2 =
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new ProcessRunner<Integer,Failure2>();
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < <span class="number">3; i++)
<span class="indent"> <span class="indent"> <span class="indent"> runner2.add(<span class="keyword">new Processor2());
<span class="indent"> <span class="indent"> <span class="keyword">try {
<span class="indent"> <span class="indent"> <span class="comment">// 由于Processor2中的count为2,所以执行第三个Processor2的时候抛出异常了
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(runner2.processAll());
<span class="indent"> <span class="indent"> } <span class="keyword">catch(Failure2 e) {
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(e);
<span class="indent"> <span class="indent"> }
<span class="indent"> }
}
15 混型
混型最基本的概念:混合多个类的能力,混型的价值之一是可以将特性和行为一致地应用于多个类之上。
混型有点面向切面编程的味道。
15.1、C++中的混型
15.2、与接口混合
使用接口来产生混型的例子,Mixin类基本上是在使用代理,因此,每个混入类型都要求在Mixin中有一个相应的域,而你必须在Mixin中编写所有必须的方法,将方法调用转发给恰当的对象。
<span class="class"><span class="keyword">class <span class="title">TimeStampedImp <span class="keyword">implements <span class="title">TimeStamped {
<span class="indent"> <span class="keyword">private <span class="keyword">final <span class="keyword">long timeStamp;
<span class="indent"> <span class="keyword">public TimeStampedImp() {
<span class="indent"> <span class="indent"> timeStamp = <span class="keyword">new Date().getTime();
<span class="indent"> }
<span class="indent"> <span class="keyword">public <span class="keyword">long getStamp() { <span class="keyword">return timeStamp; }
}
<span class="class"><span class="keyword">interface <span class="title">SerialNumbered { <span class="keyword">long getSerialNumber(); }
<span class="class"><span class="keyword">class <span class="title">SerialNumberedImp <span class="keyword">implements <span class="title">SerialNumbered {
<span class="indent"> <span class="keyword">private <span class="keyword">static <span class="keyword">long counter = <span class="number">1;
<span class="indent"> <span class="keyword">private <span class="keyword">final <span class="keyword">long serialNumber = counter++;
<span class="indent"> <span class="keyword">public <span class="keyword">long getSerialNumber() { <span class="keyword">return serialNumber; }
}
<span class="class"><span class="keyword">interface <span class="title">Basic {
<span class="indent"> <span class="keyword">public <span class="keyword">void set(String val);
<span class="indent"> <span class="keyword">public String get();
}
<span class="class"><span class="keyword">class <span class="title">BasicImp <span class="keyword">implements <span class="title">Basic {
<span class="indent"> <span class="keyword">private String value;
<span class="indent"> <span class="keyword">public <span class="keyword">void set(String val) { value = val; }
<span class="indent"> <span class="keyword">public String get() { <span class="keyword">return value; }
}
Mixin类基本上是在使用代理,因此,每个混入类型都要求在Mixin中有一个相应的域。
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_15_2 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> Mixin mixin1 = <span class="keyword">new Mixin(),mixin2 = <span class="keyword">new Mixin();
<span class="indent"> <span class="indent"> mixin1.set(<span class="string">"test string 1");
<span class="indent"> <span class="indent"> mixin2.set(<span class="string">"test string 2");
<span class="indent"> <span class="indent"> System.out.println(mixin1.get() + <span class="string">" " +
<span class="indent"> <span class="indent"> mixin1.getStamp() + <span class="string">" " + mixin1.getSerialNumber());
<span class="indent"> <span class="indent"> System.out.println(mixin2.get() + <span class="string">" " +
<span class="indent"> <span class="indent"> mixin2.getStamp() + <span class="string">" " + mixin2.getSerialNumber());
<span class="indent"> }
}
缺点:当使用更复杂的混型时,代码数量会急速增加
15.3、使用装饰器模式
当你观察混型的使用方式时,就会发现混型概念好像与装饰器设计模式关系很近。
装饰器模糊使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。
某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。
装饰器是通过使用组合和形式化结构来实现的,而混型是基于继承的。
因此可以将基于参数化类型的混型当做是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。
上一节的例子可以改写为使用装饰器:
<span class="class"><span class="keyword">class <span class="title">Decorator <span class="keyword">extends <span class="title">Basic {
<span class="indent"> <span class="keyword">protected Basic basic;
<span class="indent"> <span class="keyword">public Decorator(Basic basic) { <span class="keyword">this.basic = basic; }
<span class="indent"> <span class="keyword">public <span class="keyword">void set(String val) { basic.set(val); }
<span class="indent"> <span class="keyword">public String get() { <span class="keyword">return basic.get(); }
}
<span class="class"><span class="keyword">class <span class="title">TimeStamped <span class="keyword">extends <span class="title">Decorator {
<span class="indent"> <span class="keyword">private <span class="keyword">final <span class="keyword">long timeStamp;
<span class="indent"> <span class="keyword">public TimeStamped(Basic basic) {
<span class="indent"> <span class="indent"> <span class="keyword">super(basic);
<span class="indent"> <span class="indent"> timeStamp = <span class="keyword">new Date().getTime();
<span class="indent"> }
<span class="indent"> <span class="keyword">public <span class="keyword">long getStamp() { <span class="keyword">return timeStamp; }
}
<span class="class"><span class="keyword">class <span class="title">SerialNumbered <span class="keyword">extends <span class="title">Decorator {
<span class="indent"> <span class="keyword">private <span class="keyword">static <span class="keyword">long counter = <span class="number">1;
<span class="indent"> <span class="keyword">private <span class="keyword">final <span class="keyword">long serialNumber = counter++;
<span class="indent"> <span class="keyword">public SerialNumbered(Basic basic) { <span class="keyword">super(basic); }
<span class="indent"> <span class="keyword">public <span class="keyword">long getSerialNumber() { <span class="keyword">return serialNumber; }
}
<span class="class"><span class="keyword">class <span class="title">Decoration {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> TimeStamped t = <span class="keyword">new TimeStamped(<span class="keyword">new Basic());
<span class="indent"> <span class="indent"> TimeStamped t2 = <span class="keyword">new TimeStamped(
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new SerialNumbered(<span class="keyword">new Basic()));
<span class="indent"> <span class="indent"> <span class="comment">//! t2.getSerialNumber(); // Not available
<span class="indent"> <span class="indent"> SerialNumbered s = <span class="keyword">new SerialNumbered(<span class="keyword">new Basic());
<span class="indent"> <span class="indent"> SerialNumbered s2 = <span class="keyword">new SerialNumbered(
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new TimeStamped(<span class="keyword">new Basic()));
<span class="indent"> <span class="indent"> <span class="comment">//! s2.getStamp(); // Not available
<span class="indent"> }
}
缺点:从main方法注释掉的两行代码可以发现,使用装饰器所产生的对象类型是最后被装饰的类型,尽管可以添加多个层,但是最后一层才是实际的类型,因此只有最后一层方法是可视的。因此,对于装饰器来说,其明显的缺陷是它只能有效地工作于装饰中的最后一层,而混型方法显然会更自然一些。因此,装饰器只是对由混型提出的问题的一种局限的解决方案。
15.4、与动态代理混合
可以使用动态代理来创建一种比装饰器更贴近混型模型的机制,通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。
由于动态代理的限制,每个被混入的类都必须是某个接口的实现
delegatesByMethod;
>... pairs) {
();
> pair : pairs) {
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_15_4 {
<span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> Object mixin = MixinProxy.newInstance(
<span class="indent"> <span class="indent"> tuple(<span class="keyword">new BasicImp(),Basic.class),<span class="indent"> <span class="indent"> tuple(<span class="keyword">new TimeStampedImp(),TimeStamped.class),<span class="indent"> <span class="indent"> tuple(<span class="keyword">new SerialNumberedImp(),SerialNumbered.class));
<span class="indent"> Basic b = (Basic)mixin;
<span class="indent"> TimeStamped t = (TimeStamped)mixin;
<span class="indent"> SerialNumbered s = (SerialNumbered)mixin;
<span class="indent"> b.set(<span class="string">"Hello");
<span class="indent"> System.out.println(b.get());
<span class="indent"> System.out.println(t.getStamp());
<span class="indent"> System.out.println(s.getSerialNumber());
}
}
<span class="comment">/ Output: (Sample)
Hello
1132519137015
1
/<span class="comment">//:~
缺点:因为只有动态类型而不是非静态类型才包含所有的混入类型,因此这仍不如C++的方式好,因为可以再具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型为恰当的类型。
但是,这明显更接近于真正的混型。
16、潜在类型机制
Java泛型中,当要在泛型类型上执行操作时,就会产生问题,因为擦除要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使他们继承自特定的类,或者特定的接口。在某些情况下,你最终可能会使用普通类或者普通接口,因为限定边界的泛型和可能会和指定类或接口没有任何区别。
某些编程语言提供的一种解决方法称为潜在雷系机制或结构化类型机制(鸭子类型机制:如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当做鸭子对待。)
潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以speak()和sit()即可。”由于不要求具体类型,因此代码就可以更加泛化了。
两种支持潜在类型机制的语言:Python和C++。
下面一段Python代码演示下潜在类型机制的支持:
<span class="class"><span class="keyword">class <span class="title">Robot:
<span class="indent"> <span class="function"><span class="keyword">def <span class="title">speak<span class="params">(<span class="keyword">self):
<span class="indent"> <span class="indent"> print <span class="string">"Click!"
<span class="indent"> <span class="function"><span class="keyword">def <span class="title">sit<span class="params">(<span class="keyword">self):
<span class="indent"> <span class="indent"> print <span class="string">"Clank!"
<span class="indent"> <span class="function"><span class="keyword">def <span class="title">repoduce<span class="params">(<span class="keyword">self)
<span class="indent"> <span class="indent"> pass
<span class="function"><span class="keyword">def <span class="title">perform<span class="params">(anything):
<span class="indent"> anything.spead()
<span class="indent"> anything.sit()
*<span class="regexp">/
perform的anything参数只是一个标示符,它必须能够执行perform()期望它执行的操作,因此这里隐含着一个接口,但是从来都不必显示地写出这个接口——它是潜在的。perform不关心其参数的类型,因此我们可以向它传递任何对象,只要该对象支持speak()和sit()方法,否则,得到运行时异常。
Java的泛型是后来才添加的,因此没有任何机会可以去实现任何类型的潜在类型机制。
如果试图用Java实现上面的示例,就会被强制要求使用一个类或者接口,并在边界表达式中指定它:
<span class="class"><span class="keyword">class <span class="title">PerformingDog <span class="keyword">extends <span class="title">Dog <span class="keyword">implements <span class="title">Performs {
<span class="indent"> <span class="keyword">public <span class="keyword">void speak() { System.out.println(<span class="string">"Woof!"); }
<span class="indent"> <span class="keyword">public <span class="keyword">void sit() { System.out.println(<span class="string">"Sitting"); }
<span class="indent"> <span class="keyword">public <span class="keyword">void reproduce() {}
}
<span class="class"><span class="keyword">class <span class="title">Robot <span class="keyword">implements <span class="title">Performs {
<span class="indent"> <span class="keyword">public <span class="keyword">void speak() { System.out.println(<span class="string">"Click!"); }
<span class="indent"> <span class="keyword">public <span class="keyword">void sit() { System.out.println(<span class="string">"Clank!"); }
<span class="indent"> <span class="keyword">public <span class="keyword">void oilChange() {}
}
<span class="class"><span class="keyword">class <span class="title">Communicate {
<span class="indent"> <span class="keyword">public <span class="keyword">static
<span class="indent"> <span class="keyword">void perform(T performer) {
<span class="indent"> <span class="indent"> performer.speak();
<span class="indent"> <span class="indent"> performer.sit();
<span class="indent"> }
}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_16 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> PerformingDog d = <span class="keyword">new PerformingDog();
<span class="indent"> <span class="indent"> Robot r = <span class="keyword">new Robot();
<span class="indent"> <span class="indent"> Communicate.perform(d);
<span class="indent"> <span class="indent"> Communicate.perform(r);
<span class="indent"> }
}
<span class="comment">/ Output:
Woof!
Sitting
Click!
Clank!
/
注意:perform()不需要使用泛型来工作,它可以被简单的指定为接受一个Performs对象:
17、对缺乏潜在类型机制的补偿
17.1、反射
对于潜在类型机制的一种补偿,可以使用的一种方式是反射,下面的perform()方法就是用了潜在类型机制:
<span class="class"><span class="keyword">class <span class="title">SmartDog {
<span class="indent"> <span class="keyword">public <span class="keyword">void speak() { System.out.println(<span class="string">"Woof!"); }
<span class="indent"> <span class="keyword">public <span class="keyword">void sit() { System.out.println(<span class="string">"Sitting"); }
<span class="indent"> <span class="keyword">public <span class="keyword">void reproduce() {}
}
<span class="class"><span class="keyword">class <span class="title">CommunicateReflectively {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void perform(Object speaker) {
<span class="indent"> <span class="indent"> Class<?> spkr = speaker.getClass();
<span class="indent"> <span class="indent"> <span class="keyword">try {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">try {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> Method speak = spkr.getMethod(<span class="string">"speak");
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> speak.invoke(speaker);
<span class="indent"> <span class="indent"> <span class="indent"> } <span class="keyword">catch(NoSuchMethodException e) {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> System.out.println(speaker + <span class="string">" cannot speak");
<span class="indent"> <span class="indent"> <span class="indent"> }
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">try {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> Method sit = spkr.getMethod(<span class="string">"sit");
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> sit.invoke(speaker);
<span class="indent"> <span class="indent"> <span class="indent"> } <span class="keyword">catch(NoSuchMethodException e) {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> System.out.println(speaker + <span class="string">" cannot sit");
<span class="indent"> <span class="indent"> <span class="indent"> }
<span class="indent"> <span class="indent"> } <span class="keyword">catch(Exception e) {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">throw <span class="keyword">new RuntimeException(speaker.toString(),e);
<span class="indent"> <span class="indent"> }
<span class="indent"> }
}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_17_1 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> CommunicateReflectively.perform(<span class="keyword">new SmartDog());
<span class="indent"> <span class="indent"> CommunicateReflectively.perform(<span class="keyword">new Robot());
<span class="indent"> <span class="indent"> CommunicateReflectively.perform(<span class="keyword">new Mime());
<span class="indent"> }
}
<span class="comment">/ Output:
Woof!
Sitting
Click!
Clank!
Mime cannot speak
Pretending to sit
/<span class="comment">//:~
17.2、将一个方法应用于序列
上一节通过反射类型实现的潜在类型机制把所有类型检查都转移到了运行时,因此许多情况下并不是我们所希望的。
下面创建一个apply()方法,它能够将任何方法f应用于某个序列seq中的所有对象,通过反射和可变参数args传递方法的参数来实现。
>
<span class="class"><span class="keyword">class <span class="title">Shape {
<span class="indent"> <span class="keyword">public <span class="keyword">void rotate() { System.out.println(<span class="keyword">this + <span class="string">" rotate"); }
<span class="indent"> <span class="keyword">public <span class="keyword">void resize(<span class="keyword">int newSize) {
<span class="indent"> <span class="indent"> System.out.println(<span class="keyword">this + <span class="string">" resize " + newSize);
<span class="indent"> }
}
<span class="class"><span class="keyword">class <span class="title">Square <span class="keyword">extends <span class="title">Shape {}
<span class="class"><span class="keyword">class <span class="title">FilledList<<span class="title">T> <span class="keyword">extends <span class="title">ArrayList<<span class="title">T> {
<span class="indent"> <span class="comment">// 类型标记技术是Java文献推荐的技术。但是,有些人强烈地首先工厂方式
<span class="indent"> <span class="keyword">public FilledList(Class<? extends T> type,<span class="keyword">int size) {
<span class="indent"> <span class="indent"> <span class="keyword">try {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < size; i++)
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="comment">// Assumes default constructor:
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> add(type.newInstance());
<span class="indent"> <span class="indent"> } <span class="keyword">catch(Exception e) {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">throw <span class="keyword">new RuntimeException(e);
<span class="indent"> <span class="indent"> }
<span class="indent"> }
}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_17_2 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) <span class="keyword">throws Exception {
<span class="indent"> <span class="indent"> List shapes = <span class="keyword">new ArrayList();
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < <span class="number">10; i++)
<span class="indent"> <span class="indent"> <span class="indent"> shapes.add(<span class="keyword">new Shape());
<span class="indent"> <span class="indent"> Apply.apply(shapes,Shape.class.getMethod(<span class="string">"rotate"));
<span class="indent"> <span class="indent"> Apply.apply(shapes,<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> Shape.class.getMethod(<span class="string">"resize",<span class="keyword">int.class),<span class="number">5);
<span class="indent"> <span class="indent"> List squares = <span class="keyword">new ArrayList();
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < <span class="number">10; i++)
<span class="indent"> <span class="indent"> <span class="indent"> squares.add(<span class="keyword">new Square());
<span class="indent"> <span class="indent"> Apply.apply(squares,Shape.class.getMethod(<span class="string">"rotate"));
<span class="indent"> <span class="indent"> Apply.apply(squares,<span class="number">5);
<span class="indent"> <span class="indent"> Apply.apply(<span class="keyword">new FilledList(Shape.class,<span class="number">10),<span class="indent"> <span class="indent"> Shape.class.getMethod(<span class="string">"rotate"));
<span class="indent"> <span class="indent"> Apply.apply(<span class="keyword">new FilledList(Square.class,<span class="indent"> <span class="indent"> Shape.class.getMethod(<span class="string">"rotate"));
<span class="indent"> <span class="indent"> SimpleQueue shapeQ = <span class="keyword">new SimpleQueue();
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < <span class="number">5; i++) {
<span class="indent"> <span class="indent"> <span class="indent"> shapeQ.add(<span class="keyword">new Shape());
<span class="indent"> <span class="indent"> <span class="indent"> shapeQ.add(<span class="keyword">new Square());
<span class="indent"> <span class="indent"> }
<span class="indent"> <span class="indent"> Apply.apply(shapeQ,Shape.class.getMethod(<span class="string">"rotate"));
<span class="indent"> }
}
17.3、当你并未碰巧拥有正确的接口时
上一节示例的Iterable接口是内建的,如果刚好不存在适合你的接口的时候呢?
下面的例子中,没有预见到对“Addable”接口的需要,所以我们被限制在Collection继承层次结构之内,即便SimpleQueue有一个add()方法,它也不能工作。因为这会将代码限制为只能工作于Collection,因此这样的代码不是特别的繁华。有了潜在类型机制,情况就会不同了。
collection, classToken,<span class="class"><span class="keyword">class <span class="title">Contract {
<span class="indent"> <span class="keyword">private <span class="keyword">static <span class="keyword">long counter = <span class="number">0;
<span class="indent"> <span class="keyword">private <span class="keyword">final <span class="keyword">long id = counter++;
<span class="indent"> <span class="keyword">public String toString() {
<span class="indent"> <span class="indent"> <span class="keyword">return getClass().getName() + <span class="string">" " + id;
<span class="indent"> }
}
<span class="class"><span class="keyword">class <span class="title">TitleTransfer <span class="keyword">extends <span class="title">Contract {}
<span class="keyword">public <span class="class"><span class="keyword">class <span class="title">Chapter15_17_3 {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) <span class="keyword">throws Exception {
<span class="indent"> <span class="indent"> List contracts = <span class="keyword">new ArrayList();
<span class="indent"> <span class="indent"> Fill.fill(contracts,Contract.class,<span class="number">3);
<span class="indent"> <span class="indent"> Fill.fill(contracts,TitleTransfer.class,<span class="number">2);
<span class="indent"> <span class="indent"> <span class="keyword">for(Contract c: contracts)
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(c);
<span class="indent"> <span class="indent"> SimpleQueue contractQueue =
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new SimpleQueue();
<span class="indent"> <span class="indent"> <span class="comment">// Won't work. fill() is not generic enough:
<span class="indent"> <span class="indent"> <span class="comment">// Fill.fill(contractQueue,3);
<span class="indent"> }
}
<span class="comment">/ Output:
Contract 0
Contract 1
Contract 2
TitleTransfer 3
TitleTransfer 4
/<span class="comment">//:~
17.4、用适配器仿真潜在类型机制
实际上,潜在类型机制创建了一个包含所需方法的隐式接口。因此它遵循这样的规则:如果我们手工编写了必须的接口,那么它就应该能够解决问题。
从我们拥有的接口中编写代码来产生我们需要的接口,这是适配器设计模式的一个典型示例。我们可以使用适配器来适配已有的接口,以产生想要的接口。
首先创建一个Addable接口,具体的实现由适配器提供。
{ <span class="class"><span class="keyword">class <span class="title">Fill2 {
<span class="indent"> <span class="comment">// Classtoken version:
<span class="indent"> <span class="comment">// 用Addable取代前一节的Collection
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void fill(Addable addable,<span class="keyword">int size) {
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < size; i++)
<span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">try {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> addable.add(classToken.newInstance());
<span class="indent"> <span class="indent"> <span class="indent"> } <span class="keyword">catch(Exception e) {
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">throw <span class="keyword">new RuntimeException(e);
<span class="indent"> <span class="indent"> <span class="indent"> }
<span class="indent"> }
<span class="indent"> <span class="comment">// Generator version:
<span class="indent"> <span class="comment">// 重载的fill,接受一个Generator而不是标记类型。
<span class="indent"> <span class="comment">// 编译器将确保传递的是正确的Generator,因此不会抛出任何异常。
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void fill(Addable addable,<span class="indent"> <span class="indent"> <span class="indent"> Generator generator,<span class="keyword">int size) {
<span class="indent"> <span class="indent"> <span class="keyword">for(<span class="keyword">int i = <span class="number">0; i < size; i++)
<span class="indent"> <span class="indent"> <span class="indent"> addable.add(generator.next());
<span class="indent"> }
}
<span class="comment">// To adapt a base type,you must use composition.
<span class="comment">// Make any Collection Addable using composition:
<span class="comment">// 创建一个Collection的Addable适配器
<span class="class"><span class="keyword">class <span class="title">AddableCollectionAdapter<<span class="title">T> <span class="keyword">implements <span class="title">Addable<<span class="title">T> {
<span class="indent"> <span class="keyword">private Collection c;
<span class="indent"> <span class="keyword">public AddableCollectionAdapter(Collection c) {
<span class="indent"> <span class="indent"> <span class="keyword">this.c = c;
<span class="indent"> }
<span class="indent"> <span class="keyword">public <span class="keyword">void add(T item) { c.add(item); }
}
<span class="comment">// A Helper to capture the type automatically:
<span class="class"><span class="keyword">class <span class="title">Adapter {
<span class="indent"> <span class="keyword">public <span class="keyword">static
<span class="indent"> Addable collectionAdapter(Collection c) {
<span class="indent"> <span class="indent"> <span class="keyword">return <span class="keyword">new AddableCollectionAdapter(c);
<span class="indent"> }
}
<span class="comment">// To adapt a specific type,you can use inheritance.
<span class="comment">// Make a SimpleQueue Addable using inheritance:
<span class="class"><span class="keyword">class <span class="title">AddableSimpleQueue<<span class="title">T>
<span class="indent"> <span class="keyword">extends <span class="title">SimpleQueue<<span class="title">T> <span class="keyword">implements <span class="title">Addable<<span class="title">T> {
<span class="indent"> <span class="keyword">public <span class="keyword">void add(T item) { <span class="keyword">super.add(item); }
}
<span class="class"><span class="keyword">class <span class="title">Fill2Test {
<span class="indent"> <span class="keyword">public <span class="keyword">static <span class="keyword">void main(String[] args) {
<span class="indent"> <span class="indent"> <span class="comment">// Adapt a Collection:
<span class="indent"> <span class="indent"> List carrier = <span class="keyword">new ArrayList();
<span class="indent"> <span class="indent"> <span class="comment">// 使用Addable的Collection适配器
<span class="indent"> <span class="indent"> Fill2.fill(
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new AddableCollectionAdapter(carrier),<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> Coffee.class,<span class="number">3);
<span class="indent"> <span class="indent"> <span class="comment">// Helper method captures the type:
<span class="indent"> <span class="indent"> Fill2.fill(Adapter.collectionAdapter(carrier),<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> Latte.class,<span class="number">2);
<span class="indent"> <span class="indent"> <span class="keyword">for(Coffee c: carrier)
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(c);
<span class="indent"> <span class="indent"> System.out.println(<span class="string">"----------------------");
<span class="indent"> <span class="indent"> <span class="comment">// Use an adapted class:
<span class="indent"> <span class="indent"> AddableSimpleQueue coffeeQueue =
<span class="indent"> <span class="indent"> <span class="indent"> <span class="indent"> <span class="keyword">new AddableSimpleQueue();
<span class="indent"> <span class="indent"> Fill2.fill(coffeeQueue,Mocha.class,<span class="number">4);
<span class="indent"> <span class="indent"> Fill2.fill(coffeeQueue,Latte.class,<span class="number">1);
<span class="indent"> <span class="indent"> <span class="keyword">for(Coffee c: coffeeQueue)
<span class="indent"> <span class="indent"> <span class="indent"> System.out.println(c);
<span class="indent"> }
}
<span class="comment">/* Output:
Coffee 0
Coffee 1
Coffee 2
Latte 3
Latte 4
Mocha 5 Mocha 6 Mocha 7 Mocha 8 Latte 9 *///:~
18、将函数对象用作策略
{ T combine(T x,T y); }
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|