在 Objective-C 中,我可以这样做:
id<HTTPRequestDelegate> delegate;
说delegate
(类型的变量id
)符合HTTPRequestDelegate
协议(或HTTPRequestDelegate
用Java语言实现接口)。
这样,每当我将HTTPRequestDelegate
协议定义的消息发送到delegate
时,编译器都会理解该delegate
响应。
我如何在 Java 中做到这一点,即鸭子类型/动态类型?
在 Objective-C 中,我可以这样做:
id<HTTPRequestDelegate> delegate;
说delegate
(类型的变量id
)符合HTTPRequestDelegate
协议(或HTTPRequestDelegate
用Java语言实现接口)。
这样,每当我将HTTPRequestDelegate
协议定义的消息发送到delegate
时,编译器都会理解该delegate
响应。
我如何在 Java 中做到这一点,即鸭子类型/动态类型?
Java 中不存在鸭子类型。如果一个类实现了一个接口,它必须声明这个接口已经实现。仅仅拥有与接口中的方法具有相同签名的方法是不够的。
不过,接口是一种类型,您可以声明这种类型的变量。例如:
List<String> myList;
声明一个myList
类型的变量List<String>
,其中List
是一个接口。
您可以使用任何实现此 List 接口的对象来初始化此变量:
myList = new ArrayList<String>();
但随后ArrayList
必须声明它实现了List
接口(它确实如此)。
//Static typing
HTTPRequestDelegate delegate;
Interface a = new Implementation();
Java 没有鸭子类型的概念。您必须将实例强制转换为已知类型。
在 Objective-C 中,类型由两部分组成: 1) 类指针类型(例如NSObject *
、NSString *
等);this 也可以是id
,这是一种特殊类型,可以接受任何对象指针并禁用调用方法的静态类型编译器警告;和 2) 可选地,对象符合的一个或多个协议(类似于 Java 中的接口)(例如<NSCopying, NSCoding>
)
在 Java 中,引用类型是类名或接口名。(你只能选择一个。)类和接口之间没有太多的分离。
在您的情况下,您的对象指针类型是id
,它不表示任何信息,并且您指定了一个接口,HTTPRequestDelegate
。这在 Java 中可以等价地表示为
HTTPRequestDelegate delegate;
如果您指定了多个协议,或者您指定了一个实际的类指针类型加上一个或多个协议,那么您的类型是“交集类型”,即您指定的多种类型的交集。在这种情况下,它会更难,因为在 Java 中没有简单的方法来表达交集类型。(尽管可以在泛型类型边界中指定交集类型,例如class Foo<T extends Collection & Comparable & CharSequence>
)
除此之外,Objective-C 和 Java 之间唯一的其他区别是,在 Objective-C 中,您可以在对象指针上发送任何消息(即调用任何方法)并且是允许的,即使变量的静态类型确实如此不表示它受支持(如果您使用实际的类指针类型,编译器会发出警告;如果使用id
它不会发出警告)。我想这就是你所说的动态类型。而在 Java 中,您只能在编译时调用已知静态类型支持的方法。
但是,如果您使用类似 的类型id<HTTPRequestDelegate>
,那么您很可能只打算使用提供的方法HTTPRequestDelegate
,因此您没有使用任何动态类型功能。因此,在 Java 中,只需 HTTPRequestDelegate 就足够了。
我假设那个委托没有明确地实现你想要的接口。
您可以创建一个实现接口的新类并扩展您想要的实现类(或具有实现类并显式调用接口中的适当方法)。
如果这不是您想要的,您可能需要进行健康的反思。看看 java.lang.reflect.Proxy 和 InvocationHandler。
如果您正在寻找一种简写方式来避免使用组合显式实现接口的方法,Java 并没有真正为此提供语法支持。你必须是明确的。
如果您确实想采用大量反射的方式(不建议过度输入),请查看 Mockito。
已经给出的大多数答案都是正确的。如果一个对象实现了一个接口,那么您可以在任何需要实现该接口的地方使用该对象。鉴于 Java 的强类型系统,这是最自然的方法。
为了与List
/的示例保持一致ArrayList
,您可以创建一个ArrayList
对象,然后在需要 a 的任何地方使用它List
——或者,基于其他实现的接口,Serializable
、Cloneable
、Iterable
、Collection
或RandomAccess
。考虑到超类,ArrayList
可以将 的实例用作AbstractList
、AbstractCollection
或java.lang.Object
。
反射可以与动态代理对象一起使用,将具有正确方法的对象楔入鸭子服装中。这将类型检查转移到运行时,并且通常有更好的理由使用普通类型系统而不是反对它。
因为这听起来很有趣,所以这里是一个将非 Duck 包装在代理对象中的示例。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DuckDemo {
public static Duck getDuckProxy(final Object duckLike) {
final InvocationHandler invocationHandler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> actualClass = duckLike.getClass();
String methodName = method.getName();
Class[] parameterTypes = method.getParameterTypes();
Method requested = actualClass.getDeclaredMethod (methodName, parameterTypes);
return requested.invoke(duckLike, args);
}
};
final ClassLoader originalObjectClassLoader = duckLike.getClass().getClassLoader();
Duck woodenDuck = (Duck) Proxy.newProxyInstance(
originalObjectClassLoader,
new Class[] { Duck.class },
invocationHandler
);
return woodenDuck;
}
private interface Duck {
void quack();
};
public static void makeItQuack (Duck duck) {
duck.quack();
}
public static void main (String args[]) {
Object quacksLikeADuck = new Object() {
void quack() {
System.out.println ("Quack!");
}
};
// Does not compile -- makeItQuack(DuckDemo.Duck) [...] cannot be applied to (java.lang.Object)
// makeItQuack (quacksLikeADuck);
// Runtime java.lang.ClassCastException: [...] cannot be cast to GenericProxyFactory$Duck
// makeItQuack ((Duck)quacksLikeADuck);
Duck d = getDuckProxy(quacksLikeADuck);
makeItQuack (d);
}
}
值得一提的是,IBM developerWorks 也有一篇关于动态代理主题的好文章。
我认为这里有很多术语要解开。Java不允许你有一个原始指针,只有一个引用,它有一个类型。
无论如何,假设您引用了一个您知道 implements 的实例HTTPRequestDelegate
。你可以像这样投射它:
HTTPRequestDelegate delegate = (HTTPRequestDelegate) ref;
括号中的位是演员表。只要delegate
在HTTPRequestDelegate
.
Java 程序员做鸭式类型的另一种方法是反射,但如果你知道接口,那么大小写就是要走的路。