Имитация утиного ввода в Java

Проблема: Я хотел бы иметь возможность общего доступа в Java к любому свойству/полю Java ojbect так же, как и к динамическому языку (думаю, Groovy, JavaScript). Я не буду знать, когда я пишу этот сантехнический код, какой тип объекта он или что имя свойства/поля. Но я буду знать имя свойства/поля, когда я его использую.

Мое текущее решение: До сих пор я написал простой класс-оболочку, который использует java.beans.Introspector, чтобы захватить свойства Bean/POJO и выставить их как Map<String, Object>. Он грубоват, но работает для простых случаев.

Мой вопрос заключается в том, что существуют другие методологии для решения этой проблемы, кроме отражения/преобразования на карту?

Прежде чем я пойду слишком дальше по этому пути, я хотел бы знать, знает ли кто-нибудь, как я могу cannibalize что-то из Rhino или, возможно, javax.script.*, у которого хорошо продуманная реализация этой концепции. Или, возможно, совсем другой подход, который я не рассматривал.

Изменить: да Я знаком с отражением (я считаю, что Introspector использует под капотом в любом случае). Мне было просто любопытно, были ли какие-то другие хорошо продуманные решения.

Изменить 2:. Похоже, что наиболее популярные ответы включают в себя 1) отражение либо напрямую, либо через вспомогательные классы, и/или 2) сопоставление с интерфейсами, которые реализуют требуемые члены класса. Я действительно заинтригован комментарием, в котором говорится об использовании Groovy. Поскольку Groovy имеет истинное утиное печатание и это язык JVM, есть ли способ сделать простой помощник в Groovy и вызвать его из Java? Это было бы действительно здорово и, вероятно, было бы более гибким и лучше работать.

Ответ: Я отметил, что Майк отвечает как лучший, поскольку это полная концепция, которая приближается. Я, вероятно, не поеду по этому пути, но это, безусловно, полезный подход. Любой, кто просматривает это сообщение, должен обязательно прочитать разговоры здесь, так как здесь есть много полезной информации.

Спасибо!

Ответ 1

Если вам известен набор 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();
    }
  }
}

Ответ 2

Еще один метод, с которым я только сталкивался, который использует стирание типа "злоупотребления?", является интересным:

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

Я не уверен, что я покупаю, что это сильно отличается от простого использования интерфейсов напрямую, но, возможно, это полезно для кого-то другого.

Ответ 3

Взгляните на методы java.lang.Class и API отражения: java.lang.reflect. *