在 Java 中模拟鸭子类型

发布于 2024-10-05 07:51:59 字数 932 浏览 0 评论 0原文

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

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

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

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

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

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

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

谢谢!

The problem: I'd like to be able to generically access in Java any property/field on a Java ojbect similarly to how a dynamic language (think Groovy, JavaScript) would. I won't know at the time I'm writing this plumbing code what type of object it is or what the property/field name will be. But I will know the property/field name when I go to use it.

My current solution: So far I've written a simple wrapper class that uses java.beans.Introspector to grab the properties of a Bean/POJO and expose them as a Map<String, Object>. It's crude but works for simple cases.

My question is what other methodologies are there for approaching this problem besides reflection / converting to a Map?

Before I go too much further down this path, I'd like to know if anyone knows how I could cannibalize something out of Rhino or perhaps javax.script.* which has a well thought out implementation of this concept. Or perhaps an entirely different approach that I haven't considered.

Edit: yes I'm familiar with reflection (which I believe is what Introspector is using under the hood anyway). I was just curious if there was any other well thought out solutions.

Edit 2: It appears that the most popular answers involve 1) reflection either directly or via helper classes, and/or 2) mapping to interfaces which implement the desired class members. I'm really intrigued by the comment which talks about leveraging Groovy. Since Groovy has true duck-typing and it is a JVM language, is there a way to make a simple helper in Groovy and call it from Java? This would be really cool and probably more flexible and perform better.

Answer: I marked Mike's answer as the best since it is a complete concept which comes the closest. I probably won't go that route for this particular case, but it is certainly a useful approach. Anyone looking through this should be sure to read the conversations on here as there is a lot of useful info in there as well.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

一绘本一梦想 2024-10-12 07:51:59

如果您知道要公开的 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();
    }
  }
}

If you know the set of APIs that you want to expose, say you know you want access to a length method and an iterator method, you can define an interface:

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

and you want to be able to use this interface to access corresponding methods on instances that do not implement this interface, you can use Proxy classes : http://download.oracle.com/javase/1.4.2/docs/api/java/lang/reflect/Proxy.html

So you create a proxy

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);
      }
    });

Then you can use the wrapper as you would the duck in a dynamically typed language.

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

Here's a full length runnable example that prints "Quack" four times using a wrapper:

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();
    }
  }
}
烧了回忆取暖 2024-10-12 07:51:59

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

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

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

Another method that I just came across which leverages (abuses?) type erasure is kind of interesting:

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

I'm not sure that I buy that this is much different from simply using the interfaces directly but perhaps it is useful to someone else.

小矜持 2024-10-12 07:51:59

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

Take a look at the methods of java.lang.Class and at the reflection API: java.lang.reflect.*

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文