鸭子类型是一种编程范式,它强调对象的行为而不是其类型。这种思想可以追溯到James Whitcomb Riley的名言:“当看到一个鸟走路像鸭子,游泳像鸭子,叫声也像鸭子,就称它为鸭子。” 这种类型的概念在多种编程语言中得到了支持或提倡,如Python、Ruby、PHP、C#等。尽管作者个人更倾向于静态类型,因为其在编译时有更好的类型安全性,但鸭子类型在某些场景下也有其合理之处。例如,它允许在没有类继承约束的情况下使用多态性。那么,为什么不在Java中也保留这种选项呢?
让通过一个例子来说明这个概念。假设有一个鸭子接口:
public interface Duck {
void quack();
}
然后有几个具体的动物类,其中一些可以“叫”(quack),但它们都没有显式实现鸭子接口:
public class SilentDuck {
public void quack() {
System.out.println("quack");
}
}
public class LoudDuck {
public void quack() {
System.out.println("QUACK");
}
}
public class AverageDog {
public void bark() {
System.out.println("bark");
}
}
public class TalentedDog {
public void bark() {
System.out.println("superior bark");
}
public void quack() {
System.out.println("quack-like sound");
}
}
期望是,由于SilentDuck、LoudDuck和TalentedDog都可以“叫”,应该能够通过鸭子接口使用这些对象,即使它们的类没有显式实现该接口。
Duck duck1 = attach(Duck.class, new SilentDuck());
Duck duck2 = attach(Duck.class, new LoudDuck());
Duck duckImpersonator = attach(Duck.class, new TalentedDog());
duck1.quack();
duck2.quack();
duckImpersonator.quack();
List ducks = Arrays.asList(duck1, duck2, duckImpersonator);
AverageDog不能“叫”。如果尝试将其附加到鸭子接口,期望在运行时出现异常。
Duck wannabeDuck = attach(Duck.class, new AverageDog());
// throws exception - no quack()
虽然鸭子类型有其优点,但它也有很多缺点。类型检查是在运行时而不是编译时进行的。具体类没有直接引用动态接口,简单地重命名它们中的方法可能会导致客户端代码在运行时出现问题。依赖这种编码风格需要更加谨慎的实践。
public class DynamicInterface {
public static <I> I attach(Class<I> i, Object o) {
try {
ensureMethodsExist(i, o);
ensureIsInterface(i);
return attachInterface(i, o);
} catch (Exception e) {
throw new DynamicInterfaceException(e);
}
}
@SuppressWarnings("unchecked")
private static <I> I attachInterface(Class<I> i, Object o) {
Object proxy = Proxy.newProxyInstance(i.getClassLoader(),
new Class[]{i},
new DynamicInterfaceHandler(o));
return (I) proxy;
}
private static <I> void ensureMethodsExist(Class<I> i, Object o)
throws NoSuchMethodException {
for (Method method : i.getDeclaredMethods()) {
if (o.getClass().getMethod(method.getName(), method.getParameterTypes()) == null)
throw new NoSuchMethodException(method.getName());
}
}
private static <I> void ensureIsInterface(Class<I> i) {
if (!i.isInterface())
throw new DynamicInterfaceException(i.getName() + " is not an interface");
}
}
class DynamicInterfaceHandler implements InvocationHandler {
private Object o;
DynamicInterfaceHandler(Object o) {
this.o = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Method oMethod = o.getClass().getMethod(method.getName(), method.getParameterTypes());
return oMethod.invoke(o, args);
}
}
public class DynamicInterfaceException extends RuntimeException {
public DynamicInterfaceException(Throwable t) {
super(t);
}
public DynamicInterfaceException(String m) {
super(m);
}
}