Как я могу рефлексивно вызвать метод для объекта Scala из Java? - PullRequest
8 голосов
/ 30 июня 2011

У меня есть объект scala, определенный следующим образом:

package com.example

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

Я знаю, что могу вызвать Foo$.MODULE$.bar() из Java, если Foo находится в пути к классам сборки и времени выполнения, но в моей ситуации Foo отсутствует в пути к классам сборки и может быть настроен или не настроен в пути к классам среды выполнения.

Из моего кода Java я хотел бы использовать отражение для вызова bar(), если оно доступно в пути к классам среды выполнения, иначе я вернусь к реализации по умолчанию.

Возможно ли это сделать?

Ответы [ 3 ]

5 голосов
/ 30 июня 2011

Вы можете сделать это с кодом, который выглядит примерно так:

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;
  }
}
2 голосов
/ 30 июня 2011

Класс Foo объекта равен com.example.Foo$, поэтому, если вы можете просто загрузить этот класс, все будет хорошо, без использования отражения:

try { 
    Class.forName("com.example.Foo$");
    String s = com.example.Foo$.MODULE$.bar();
    // ...
} catch (Exception ex) {
  String s = // fallback solution
  // ...
}
1 голос
/ 10 ноября 2014

Вдохновленный Дэвидом Карлсоном 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
}

Вот как вы используете ScalaUtil для вышеуказанного объекта Scala:

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");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...