目录
动态类型安全
异常
混型
C++中的混型
替代方案
与接口混合
使用装饰器模式
与动态代理混合
本笔记参考自: 《On Java 中文版》
在Java 5引入泛型前,老版本的Java程序中就已经存在了List等原生集合类型。这意味着,我们可以向这些老版本的代码中传递泛型集合,这无疑是存在风险的。为此,Java 5在java.util.Collections中添加了一组实用工具checked*():
这些方法的第一个参数都是被检查的集合,之后的参数会传入需要强制确保的类型。若这些方法检测到插入了不匹配的类型,就会发出异常。
这一点和Java 5之前的原生集合不同,它们只会在我们从中取出对象时报告问题。此时,我们难以找出发生问题的代码。
下面的例子展示了check*()的用法:
【例子:check*()的使用】
假设我们有一个Pet类,它有两个子类Cat和Dog。 则代码如下所示:
import reflection.pets.Cat;
import reflection.pets.Dog;
import reflection.pets.Pet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CheckedList {
// 一个Java 5之前遗留的方法:
@SuppressWarnings("unchecked")
static void oldStyleMethod(List probablyDogs) {
// 向probablyDogs集合中传入错误的Cat类型:
probablyDogs.add(new Cat());
}
public static void main(String[] args) {
List dogs1 = new ArrayList<>();
// 安静地放入了Cat类型:
oldStyleMethod(dogs1);
List dogs2 = Collections.checkedList(
new ArrayList<>(), Dog.class);
try {
oldStyleMethod(dogs2);
} catch (Exception e) {
System.out.println("发生异常:" + e);
}
// 使用基类集合不会存在问题:
List pets = Collections.checkedList(
new ArrayList<>(), Pet.class);
pets.add(new Dog());
pets.add(new Cat());
}
}
程序执行的结果是:
可以发现,oldStyleMethod(dogs1)并没有受到编译器的质疑,尽管方法内部会往dogs1集合中插入一个不匹配的Cat对象。而dogs2立刻抛出异常。
因为类型擦除,catch子句无法捕获泛型类型的异常,因为无法获知异常的确切类型。也因此,泛型类无法直接或间接地继承Throwable。
但类型参数可以用于方法声明中的throws子句。根据这个特点,我们可以编写随(受检查的)异常类型变化而变化的泛型代码:
【例子:可变化的异常】
import java.util.ArrayList;
import java.util.List;
interface Processor {
void process(List resultCollector) throws E;
}
class ProcessRunner
extends ArrayList> {
List processAll() throws E {
List resultCollector = new ArrayList<>();
for (Processor processor : this)
processor.process(resultCollector);
return resultCollector;
}
}
class Failure1 extends Exception {
}
class Processor1
implements Processor {
static int count = 3;
@Override
public void process(List resultCollector)
throws Failure1 {
if (count-- > 1)
resultCollector.add("哼!");
else
resultCollector.add("哈!");
if (count < 0)
throw new Failure1();
}
}
class Failure2 extends Exception {
}
class Processor2
implements Processor {
static int count = 2;
@Override
public void process(List resultCollector)
throws Failure2 {
if (count-- == 0)
resultCollector.add(47);
else {
resultCollector.add(11);
}
if (count < 0)
throw new Failure2();
}
}
public class ThrowGenericException {
public static void main(String[] args) {
ProcessRunner runner1 =
new ProcessRunner<>();
for (int i = 0; i < 3; i++)
runner1.add(new Processor1());
try {
System.out.println(runner1.processAll());
} catch (Failure1 e) {
System.out.println(e);
}
ProcessRunner runner2 =
new ProcessRunner<>();
for (int i = 0; i < 3; i++)
runner2.add(new Processor2());
try {
System.out.println(runner2.processAll());
} catch (Failure2 e) {
System.out.println(e);
}
}
}
程序执行的结果是:
Processor规定了会抛出异常E的方法process()。process()的结果被保存在了List
因为检查型异常的缘故,如果不使用参数化的异常,我们就无法泛化地进行如上的代码编写。
混型最基本的概念是,混合多个类的能力,生成一个可以代表混型中所有类型的类(不过,这通常是我们最后做的一件事)。
对混型的更改会应用于所有使用了该混型的类中。可以说,混型更加接近面向切面编程。
依旧是先看看混型在C++中的表现。C++会通过多重继承实现混型,除此之外,参数化类型也是一个不错的实现手段。
【例子:在C++中使用混型】
#include
#include
#include
using namespace std;
template class TimeStamped : public T {
long timeStamp;
public:
TimeStamped() {
timeStamp = time(0);
}
long getStamp() {
return timeStamp;
}
};
template class SerialNumbered : public T {
long serialNumber;
static long counter;
public:
SerialNumbered() {
serialNumber = counter++;
}
long getSerialNumber() {
return serialNumber;
}
};
// 定义静态存储,并进行初始化
template long SerialNumbered::counter = 1;
class Basic {
string value;
public:
void set(string val) {
value = val;
}
string get() {
return value;
}
};
int main() {
// 使用混型:
TimeStamped> mixin1, mixin2;
mixin1.set("test 1");
mixin2.set("test 2");
cout << mixin1.get() << ": " << mixin1.getStamp() <<
" " << mixin1.getSerialNumber() << endl;
cout << mixin2.get() << ": " << mixin2.getStamp() <<
" " << mixin2.getSerialNumber() << endl;
}
程序执行的结果是:
mixin1和mixin2具有所有混入类型的方法,这就相当于将已有的类映射到新的子类上一样。
不幸的是,由于类型擦除会丢弃基类的类型,因此在Java中,泛型类无法直接继承自泛型参数。
为了在Java中使用混型,下面将会给出一些替代的方案。
一种常见的方式是通过接口来实现泛型的效果:
【例子:使用接口实现混型的效果】
import java.util.Date;
interface TimeStamped {
long getStamp();
}
class TimeStampedImp implements TimeStamped {
private final long timeStamp;
TimeStampedImp() {
timeStamp = new Date().getTime();
}
@Override
public long getStamp() {
return timeStamp;
}
}
interface SerialNumbered {
long getSerialNumber();
}
class SerialNumberedImp implements SerialNumbered {
private static long counter = 1;
private final long serialNumber = counter++;
@Override
public long getSerialNumber() {
return serialNumber;
}
}
interface Basic {
void set(String val);
String get();
}
class BasicImp implements Basic {
private String value;
@Override
public void set(String val) {
value = val;
}
@Override
public String get() {
return value;
}
}
// 混合多个类的能力
class Mixin extends BasicImp
implements TimeStamped, SerialNumbered {
private TimeStamped timeStamp =
new TimeStampedImp();
private SerialNumbered serialNumber =
new SerialNumberedImp();
@Override
public long getStamp() {
return timeStamp.getStamp();
}
@Override
public long getSerialNumber() {
return serialNumber.getSerialNumber();
}
}
public class Mixins {
public static void main(String[] args) {
Mixin mixin1 = new Mixin(),
mixin2 = new Mixin();
mixin1.set("Test 1");
mixin2.set("Test 2");
System.out.println(mixin1.get() + ": " +
mixin1.getStamp() + " " +
mixin1.getSerialNumber());
System.out.println(mixin2.get() + ": " +
mixin2.getStamp() + " " +
mixin2.getSerialNumber());
}
}
程序执行的结果是:
在这里,Mixin类使用的是委托模式,这种模式要求每个被混入其中的类在Mixin中都有一个字段,Mixin负责把对应的任务委托给字段所代表的类。
然而,这种做法在面对复杂的混型时会导致代码量的急剧增加。
----------
||| 装饰器模式:用其他的类装饰一个可包装的类,分层叠加功能。
可以发现,装饰器模式和混型的概念有相似之处。装饰器通过组合和规范的结构(这个结构就是可装饰物和装饰器的层次结构)进行实现,而混型的实现基于继承。
装饰器是透明的,可以通过一个公共的信息集向其传递信息。
(可以将混型看做一种不要求装饰器继承结构的泛型装饰器机制)
【例子:使用装饰器重写上一个例子】
import java.util.Date;
class Basic {
private String value;
public void set(String val) {
val = value;
}
public String get() {
return value;
}
}
class Decorator extends Basic {
protected Basic basic;
Decorator(Basic basic) {
this.basic = basic;
}
@Override
public void set(String val) {
basic.set(val);
}
@Override
public String get() {
return basic.get();
}
}
class TimeStamped extends Decorator {
private final long timeStamp;
TimeStamped(Basic basic) {
super(basic);
timeStamp = new Date().getTime();
}
public long getStamp() {
return timeStamp;
}
}
class SerialNumbered extends Decorator {
private static long counter = 1;
private final long serialNumber = counter++;
SerialNumbered(Basic basic) {
super(basic);
}
public long getSerialNumber() {
return serialNumber;
}
}
public class Decoration {
public static void main(String[] args) {
TimeStamped t1 = new TimeStamped(new Basic());
TimeStamped t2 = new TimeStamped(
new SerialNumbered(new Basic()));
// 该方法不可用:
// t2.getSerialNumber();
t2.getStamp();
SerialNumbered s1 = new SerialNumbered(new Basic());
SerialNumbered s2 = new SerialNumbered(
new TimeStamped(new Basic()));
// 同样不可用:
// s2.getStamp();
s2.getSerialNumber();
}
}
通过这种方式创建的类也会包含所有所需的方法,但是使用装饰器产生的对象类型是其层次结构上的最后一层包装。换言之,因为只有最后一层是实际的类型,因此只有最后一层的方法是可见的。
----------
通过动态代理(可见笔记17-3),可以创建一种更捷径混型的机制。使用了动态代理,获得的结果类的动态类型将会是混合后的合并类型。
需要注意的是,在动态代理中每个被混入的类都必须是某个接口的实现。
【例子:使用混合代理实现混型】
import onjava.Tuple2;
import static onjava.Tuple.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
class MixinProxy implements InvocationHandler {
Map delegateByMethod;
@SuppressWarnings("unchecked")
MixinProxy(Tuple2
程序执行的结果是:
然而,这种实现只对动态类型有效,而不会包括静态类型。除此之外,如main()中所展示的:
在使用方法之前,我们还需要强制向下转型,这也会带来多余的麻烦。
为了支持Java的混型,业界开发了不止一个用于支持泛型的附加语言。