用Java模拟鸭子打字

mck*_*mey 10 java groovy dynamic-languages duck-typing rhino

问题:我希望能够在Java中一般地访问Java ojbect上的任何属性/字段,类似于动态语言(想想Groovy,JavaScript)的方式.我不知道当时我正在编写这个管道代码它是什么类型的对象或属性/字段名称是什么.但是当我去使用它时,我会知道属性/字段名称.

我目前的解决方案:到目前为止,我已经编写了一个简单的包装类,用于java.beans.Introspector获取Bean/POJO的属性并将它们公开为Map<String, Object>.它很粗糙但适用于简单的情况.

我的问题是除了反射/转换为Map之外还有哪些方法可以解决这个问题?

在我走上这条道路之前,我想知道是否有人知道我如何能够从Rhino中蚕食一些东西,或者javax.script.*是否有一个经过深思熟虑的实施这个概念的东西.或者也许是一种我没有考虑过的完全不同的方法.

编辑:是的我熟悉反射(我相信它是Introspector在引擎盖下使用的东西).如果还有其他经过深思熟虑的解决方案,我只是很好奇.

编辑2:似乎最流行的答案涉及1)直接或通过辅助类反映,和/或2)映射到实现所需类成员的接口.关于利用Groovy的评论,我真的很感兴趣.由于Groovy具有真正的duck-typing并且它是一种JVM语言,有没有办法在Groovy中创建一个简单的帮助器并从Java调用它?这将非常酷,可能更灵活,性能更好.

答:我认为Mike的答案是最好的,因为它是一个最接近的完整概念.对于这种特殊情况,我可能不会采用这种方式,但它肯定是一种有用的方法.任何看过这个的人都应该确保阅读这里的对话,因为那里有很多有用的信息.

谢谢!

Mik*_*uel 10

如果您知道要公开的API集,例如您知道要访问length方法和迭代器方法,则可以定义接口:

public interface TheInterfaceIWant {
  int length();
  void quack();
}
Run Code Online (Sandbox Code Playgroud)

并且您希望能够使用此接口访问未实现此接口的实例上的相应方法,您可以使用代理类:http://download.oracle.com/javase/1.4.2/docs/api/java /lang/reflect/Proxy.html

所以你创建了一个代理

final Object aDuck = ...;
TheInterfaceIWant aDuckWrapper = (TheInterfaceIWant) Proxy.newProxyInstance(
    TheInterfaceIWant.class.getClassLoader(),
    new Class[] { TheInterfaceIWant.class },
    new InvocationHandler() {
      public Object invoke(
          Object proxy, Method method, Object[] args)
          throws Throwable {
        return aDuck.getClass().getMethod(
            method.getName(), method.getParameterTypes()).invoke(aDuck, args);
      }
    });
Run Code Online (Sandbox Code Playgroud)

然后你可以像使用动态类型语言中的鸭子一样使用包装器.

if (aDuckWrapper.length() > 0) {
  aDuckWrapper.quack();
}
Run Code Online (Sandbox Code Playgroud)

这是一个全长的可运行示例,使用包装器打印"Quack"四次:

import java.lang.reflect.*;

public class Duck {

  // The interface we use to access the duck typed object.
  public interface TheInterfaceIWant {
    int length();
    void quack();
  }

  // The underlying instance that does not implement TheInterfaceIWant!
  static final class Foo {
    public int length() { return 4; }
    public void quack() { System.out.println("Quack"); }
  }

  public static void main(String[] args) throws Exception {
    // Create an instance but cast away all useful type info.
    final Object aDuck = new Foo();

    TheInterfaceIWant aDuckWrapper = (TheInterfaceIWant) Proxy.newProxyInstance(
        TheInterfaceIWant.class.getClassLoader(),
        new Class[] { TheInterfaceIWant.class },
        new InvocationHandler() {
          public Object invoke(
              Object proxy, Method method, Object[] args)
              throws Throwable {
            return aDuck.getClass().getMethod(
                method.getName(), method.getParameterTypes()).invoke(aDuck, args);
          }
        });

    for (int n = aDuckWrapper.length(); --n >= 0;) {
      // Calling aDuck.quack() here would be invalid since its an Object.
      aDuckWrapper.quack();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)