2011-06-30 17 views
8

que tienen un objeto Scala se define como la siguiente:¿Cómo puedo llamar reflexivamente un método en un objeto Scala de Java?

package com.example 

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

Yo sé que puedo llamar Foo$.MODULE$.bar() desde Java si Foo es en la construcción y el tiempo de ejecución ruta de clases, pero en mi situación Foo no está en la ruta de clase de construcción y puede o no estar configurado en el classpath en tiempo de ejecución.

Desde mi código de Java, me gustaría usar el reflejo para llamar al bar() si está disponible en el classpath en tiempo de ejecución, de lo contrario recurriré a una implementación predeterminada.

¿Es posible hacer esto?

Respuesta

5

Puede hacerlo con el código que se ve algo como esto:

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; 
    } 
} 
+1

No hay necesidad de reflexión. Se puede acceder directamente al código a través de 'Foo.bar()' o 'Foo $ .MODULE $ .bar()'. – sschaef

2

La clase de objeto de Foo es com.example.Foo$ así que si sólo puede cargar esa clase, todo saldrá bien sin el uso de la reflexión:

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

Inspirado en el answer de David Carlson Creé esta clase de utilidad para los métodos de llamada en objetos Scala de forma reflexiva. La clase le permite también llamar a los métodos de Scala que esperan parámetros.

Lo puse aquí con la esperanza de que sea útil para alguien y para obtener comentarios sobre cómo mejorar el código.

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() { 
    } 
} 

Con el fin de llamar al método Scala con los parámetros que tendrá que añadir la biblioteca Scala a la trayectoria de la estructura de su proyecto Java.

Asumiendo que este es el objeto Scala al que desea llamar:

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 
} 

Así es como se utiliza el ScalaUtil en lo anterior Scala objeto:

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"); 
Cuestiones relacionadas