【问题标题】:How can I reflectively call a method on a Scala object from Java?如何从 Java 反射性地调用 Scala 对象的方法?
【发布时间】:2011-09-25 15:22:34
【问题描述】:

我有一个如下定义的 scala 对象:

package com.example

object Foo {
  def bar(): String = "Interesting Result"
}

我知道如果 Foo 在构建和运行时类路径中,我可以从 Java 调用 Foo$.MODULE$.bar(),但在我的情况下,Foo 不在构建类路径中,可能会或可能不会在运行时类路径中配置.

如果在运行时类路径上可用,我想从我的 Java 代码中使用反射来调用 bar(),否则我将回退到默认实现。

可以这样做吗?

【问题讨论】:

    标签: java scala reflection


    【解决方案1】:

    您可以使用如下所示的代码来做到这一点:

    package com.example.java;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class Example {
    
      /**
       * Returns null or the result of calling a method on a scala object from java
       */
      public String callScalaFromJava(){
        String result = null;
    
        try {
          Class<?> clazz = Class.forName("com.example.Foo$"); // Note the trailing '$'
          Method method = clazz.getDeclaredMethod("bar");
          Field field = clazz.getField("MODULE$");
          Object instance = field.get(null);
          Object obj = method.invoke(instance, new Object[] {});
    
          if (obj instanceof String) {
            result = (String) obj);
          }
    
        } catch (Exception ex) {
          // SWALLOWING
        }
        return result;
      }
    }
    

    【讨论】:

    • 不需要反思。代码可以通过Foo.bar()Foo$.MODULE$.bar()直接访问。
    【解决方案2】:

    对象 Foo 类是 com.example.Foo$,所以如果您可以加载该类,则无需使用反射一切都会好起来的:

    try { 
        Class.forName("com.example.Foo$");
        String s = com.example.Foo$.MODULE$.bar();
        // ...
    } catch (Exception ex) {
      String s = // fallback solution
      // ...
    }
    

    【讨论】:

      【解决方案3】:

      受 David Carlson 的 answer 的启发,我创建了这个实用程序类,用于反射性地调用 Scala 对象上的方法。 该类还允许您调用需要参数的 Scala 方法。

      我把它放在这里是希望它对某人有用并获得有关如何改进代码的反馈。

      ScalaUtil.java:

      package your.stuff.utils;
      
      import java.lang.reflect.Field;
      import java.lang.reflect.InvocationTargetException;
      import java.lang.reflect.Method;
      // I use SLF4J (http://www.slf4j.org/) for logging. Feel free to change this
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      /** Utilities for Scala/Java integration */
      public class ScalaUtil {
      
          private static final Logger LOG = LoggerFactory.getLogger(ScalaUtil.class);
      
          /**
           * Calls the parameterless {@code method} on a Scala {@code object}.
           * <p>
           * Returns an object of type {@code ReturnType} if the call succeeded, null
           * otherwise.
           *
           * @param object
           *            the fully qualified path to the object such as
           *            "eu.test.MyScalaObj$". Mind the dollar sign at the end
           * @param method
           *            the name of the method inside {@code object} we want to call
           * @param <ReturnType>
           *            type of the return value of the {@code method}
           * @return the return value of the Scala {@code object}'s {@code method} or
           *         null if the {@code method} couldn't be called
           */
          public static <ReturnType> ReturnType callObj(final String object,
                  final String method) {
              final Object noParams[] = {};
              return callObj(object, method, noParams);
          }
      
          /**
           * Calls a {@code method} on a Scala {@code object} with the given method
           * {@code arguments}.
           * <p>
           * Returns an object of type {@code ReturnType} if the call succeeded, null
           * otherwise.
           *
           * @param object
           *            the fully qualified path to the object such as
           *            "eu.test.MyScalaObj$". Mind the dollar sign at the end
           * @param method
           *            the name of the method inside {@code object} we want to call
           * @param arguments
           *            the arguments that {@code method} expects
           * @param <ReturnType>
           *            type of the return value of the {@code method}
           * @return the return value of the Scala {@code object}'s {@code method} or
           *         null if the {@code method} couldn't be called
           */
          @SuppressWarnings("unchecked")
          public static <ReturnType> ReturnType callObj(final String object,
                  final String method, final Object... arguments) {
              ReturnType result = null;
              Class<?> objClass = null;
              try {
                  final Class<?>[] methArgTypes = getTypes(arguments);
                  objClass = Class.forName(object);
                  final Method meth = objClass
                          .getDeclaredMethod(method, methArgTypes);
                  final Field field = objClass.getField("MODULE$");
                  final Object instance = field.get(null);
                  result = (ReturnType) meth.invoke(instance, arguments);
              } catch (ClassNotFoundException | SecurityException
                      | NoSuchFieldException | IllegalAccessException
                      | IllegalArgumentException | InvocationTargetException e) {
                  LOG.warn("Could not call method {} on {} with arguments {}",
                          method, object, arguments, e);
              } catch (final NoSuchMethodException e) {
                  if (objClass != null) {
                      LOG.info("Declared methods: {}",
                              (Object[]) objClass.getDeclaredMethods());
                  }
                  LOG.warn("Could not call method {} on {} with arguments {}",
                          method, object, arguments, e);
              }
              return result;
          }
      
          /**
           * Returns the runtime types of some {@code objects}.
           *
           * @param objects
           *            the objects in whose types we are interested in
           * @return an array of the runtime types of the {@code objects}
           */
          private static Class<?>[] getTypes(final Object... objects) {
              final Class<?>[] types = new Class<?>[objects.length];
              for (int i = 0; i < objects.length; i++) {
                  final Object o = objects[i];
                  final Class<?> type = o.getClass();
                  types[i] = type;
              }
              return types;
          }
      
          /** This utility class is not meant to be instantiated */
          private ScalaUtil() {
          }
      }
      

      为了使用参数调用 Scala 方法,您需要将 Scala 库添加到 Java 项目的构建路径中。

      假设这是您要调用的 Scala 对象:

      package eu.tests.scala
      object ScalaObject {
      
        // You'll have to use Java's boolean when Java calls this
        def sayHello(param: java.lang.Boolean): String = "param: " + param
      
        def sayHello: String = "no param"
      
        def sayHello(param: String): String = "param: " + param
      }
      

      这是您在上述 Scala 对象上使用 ScalaUtil 的方式:

      String scalaPackage = "eu.tests.scala";
      String scalaObject = "ScalaObject";
      String method = "sayHello";
      String fullScalaObjName = scalaPackage + "." + scalaObject + "$";
      
      String result1 = ScalaUtil.callObj(fullScalaObjName, method);
      String result2 = ScalaUtil.callObj(fullScalaObjName, method, true);
      String result3 = ScalaUtil.callObj(fullScalaObjName, method, "abc");
      

      【讨论】:

        猜你喜欢
        • 2023-04-05
        • 1970-01-01
        • 1970-01-01
        • 2014-05-02
        • 2010-11-30
        • 2013-03-23
        • 2019-02-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多