Вызов полиморфных методов Scala из Java - PullRequest
2 голосов
/ 17 ноября 2011

Я пишу API, который я хочу использовать как в Scala, так и в Java. У меня есть полиморфный метод, определенный в классе Scala, и у меня возникают проблемы с вызовом его из Java.

Согласно этой совместимости FAQ на scala-lang.org, использование класса Scala, использующего расширенные языковые функции, "может быть сложным" , но не говорит, что это невозможно . Поскольку то, как передовые функции Scala переводят на Java, не документировано, предлагается разобрать файл класса, чтобы поэкспериментировать с его работой.

Пример кода

class Polymorphic {
  def polyMethod[T](implicit om: Manifest[T]) = {
    println(om.erasure.getName)
    om.erasure.newInstance
  }
}

object Polymorphic {
  def main(args: Array[String]) {
    val objOfT = (new Polymorphic).polyMethod[String]
  }
}

Это прекрасно работает в Scala, печать "java.lang.String"

Я запустил javap -c в результирующем файле класса и получил следующий список для polyMethod:

public java.lang.Object polyMethod(scala.reflect.Manifest);
  Code:
   0:   getstatic   #20; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   3:   aload_1
   4:   invokeinterface #27,  1; //InterfaceMethod scala/reflect/ClassManifest.erasure:()Ljava/lang/Class;
   9:   invokevirtual   #33; //Method java/lang/Class.getName:()Ljava/lang/String;
   12:  invokevirtual   #37; //Method scala/Predef$.println:(Ljava/lang/Object;)V
   15:  aload_1
   16:  invokeinterface #27,  1; //InterfaceMethod scala/reflect/ClassManifest.erasure:()Ljava/lang/Class;
   21:  invokevirtual   #41; //Method java/lang/Class.newInstance:()Ljava/lang/Object;
   24:  areturn

3 основных проблемы:

  1. Метод полиморфен в своем типе возврата
  2. Параметр Manifest неявный в Java
  3. На самом деле я не могу передать параметр типа.

Реальный код проекта, с которым я пытаюсь это сделать, находится здесь: https://github.com/ConnorDoyle/EnMAS/blob/master/clientServerPrototype/src/org/enmas/pomdp/State.scala

Моей конечной целью является создание универсального, но безопасного типа словаря данных. Он прекрасно работает в Scala, но мне бы очень хотелось, чтобы этот метод вызывался также из Java API. Моя мотивация дизайна - не допускать отражения, явного приведения и нулевых проверок в клиентском коде.

Редактировать: 18 ноября 2011 В ответ на комментарий @kassens ниже

Изменение определения polyMethod на следующее:

def polyMethod[T](implicit om: Manifest[T]): T = {
  println(om.erasure.getName)
  om.erasure.newInstance.asInstanceOf[T]
}

дает точно такой же байт-код, когда я проверяю файл класса с помощью javap.

Ответы [ 4 ]

4 голосов
/ 18 ноября 2011

Есть несколько проблем в игре:

  1. Унифицированные дженерики в Scala. Тот факт, что T не ограничен в Scala (что означает, что его можно создавать с помощью примитивных типов), заставляет компилятор Scala консервативно стирать его до Object. Если вы измените свой код Scala на

    def polyMethod[T <: AnyRef](implicit om: Manifest[T]) = {
       println(om.erasure.getName)
       om.erasure.newInstance.asInstanceOf[T]
    }
    

    он вернет T. Обратите внимание на явное приведение из newInstance, поскольку метод библиотеки Scala не возвращает правильный универсальный тип.

  2. Использование манифестов. Это добавляет еще один уровень обобщенных элементов, которые необходимо выровнять, чтобы ваш вызов прошел. Как предположил Дидьерд, лучше иметь перегрузку в Scala, которая вместо этого берет java.lang.Class.

Так что мой совет будет добавить это в ваш код Scala:

  def polyMethod[T <: AnyRef](cls: Class[T]): T = 
    polyMethod(Manifest.classType(cls))

А потом назовите это так из Java:

void test(Polymorphic p) { 
    JavaScalaCall c = p.polyMethod(this.getClass());
}

Как примечание: байт-код всегда стирается. Если вы хотите просмотреть общую информацию в файлах классов, найдите атрибут Signature, используя, например, jclasslib .

2 голосов
/ 18 ноября 2011

Эквивалент в java наличия аргумента манифеста - передать класс (явно увы, и он не очень хорошо работает, когда тип, который вы хотите, является универсальным типом).

Можно было бы создать манифест из Java, но лучше сделать это из Scala.Вы можете добавить

def polyMethodForJava[T](clazz: Class[T]) 
  = polyMethod(ClassManifest.From(clazz))

Конечно, использовать его будет не так удобно, как в Scala.

2 голосов
/ 18 ноября 2011

Конечно, есть некоторые методы Scala, которые нельзя вызвать из Java. См. здесь для описания одного примера (не того, с которым вы столкнулись).

В общем, если вы хотите создать интерфейс, который может использоваться "естественным образом" Java-клиентом, вам, вероятно, потребуется написать некоторые классы или методы адаптера.

0 голосов
/ 18 ноября 2011

Одно из предложений - использовать универсальный тип Java, а Box / Unbox - собственный тип данных. Используйте тип соответствия, чтобы сделать вашу операцию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...