【问题标题】:Simulating duck typing in Java在 Java 中模拟鸭子类型
【发布时间】:2011-05-18 20:04:51
【问题描述】:

问题:我希望能够像使用动态语言一样(想想 Groovy, JavaScript)会。在编写此管道代码时,我不知道它是什么类型的对象或属性/字段名称是什么。但是当我去使用它时,我会知道它的属性/字段名称。

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

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

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

编辑: 是的,我对反射很熟悉(我相信这是 Introspector 在后台使用的)。我只是好奇是否还有其他经过深思熟虑的解决方案。

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

答案:我将 Mike 的答案标记为最佳答案,因为它是最接近的完整概念。对于这种特殊情况,我可能不会走那条路,但这肯定是一种有用的方法。任何浏览此内容的人都应该确保阅读这里的对话,因为那里也有很多有用的信息。

谢谢!

【问题讨论】:

  • 这只是学术练习吗?
  • 你知道java反射吗?它允许您通过名称以任何方法访问任何字段,但如果您想对程序中的每个命令都使用它,则相当复杂。
  • 反射是要走的路,特别是如果您直到运行时才知道要调用什么。
  • JRuby 或 Groovy 等本身怎么样? IE。将它们用作代理。
  • @McKAMEY :如果您在编译时需要它,您可以生成代码以满足给定接口(甚至可以创建接口),这更像是“结构类型”(这就是我所做的)

标签: java groovy rhino dynamic-languages duck-typing


【解决方案1】:

看看 java.lang.Class 的方法和反射 API:java.lang.reflect.*

【讨论】:

    【解决方案2】:

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

    public interface TheInterfaceIWant {
      int length();
      void quack();
    }
    

    如果你希望能够使用这个接口来访问没有实现这个接口的实例上的相应方法,你可以使用代理类: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);
          }
        });
    

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

    if (aDuckWrapper.length() > 0) {
      aDuckWrapper.quack();
    }
    

    这是一个完整的可运行示例,它使用包装器将“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();
        }
      }
    }
    

    【讨论】:

    • 有趣。所以最终代理和反射/检查器之间的性能大致相同,因为它们都使用反射。但这对我们来说可能是一个很好的从一个接口到另一个接口的类型适配器。谢谢!
    • @McKAMEY,是的。不幸的是,有更多的开销。 Method.getParameterTypes() 需要对所有非无参数方法进行数组分配和复制。如果这被证明是巨大的开销,那么一旦你掌握了一些关于使用模式的数据,一些熟练的记忆应该能够减少这种开销。
    • 这种技术的最大问题(除了开销)是你仍然必须用接口支持它。它是动态的,因为您强制的类不需要从这些接口继承,但使用它的代码需要接口。这是与真正的鸭式 IMO 最大的不同。当你有一个接口和一个没有实现它的类时,它作为一个适配器工作得很好。您甚至可以在查找实际方法之前重新映射方法名称。
    • 鸭子类型适用于动态类型语言,对于静态类型语言,您应该看看“结构类型”有一个实现(whiteoak.sourceforge.net)。然而,Java 不支持结构类型。
    【解决方案3】:

    我刚刚遇到的另一种利用(滥用?)类型擦除的方法有点有趣:

    http://rickyclarkson.blogspot.com/2006/07/duck-typing-in-java-and-no-reflection.html

    我不确定我是否认为这与直接使用接口有很大不同,但也许它对其他人有用。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-20
    • 2011-02-25
    • 2020-09-30
    • 1970-01-01
    • 2015-05-21
    • 1970-01-01
    • 2010-11-07
    • 1970-01-01
    相关资源
    最近更新 更多