Как узнать об использовании scala.None из Java с использованием javap? - PullRequest
33 голосов
/ 19 февраля 2012

В предыдущем вопросе Доступ к scala.None из Java , похоже, что люди использовали javap, чтобы выяснить, как получить доступ к scala.None из Java.Я хотел бы знать, как они это сделали.К вашему сведению, ответ:

scala.Option$.MODULE$.apply(null);

, который можно закорочить на:

scala.Option.apply(null);

Для данной программы (OptionTest.scala):

object OptionTest extends App {
  val x = scala.None
  val y = scala.Some("asdf")
}

Я запустилjavap на нем так:

javap -s -c -l -private OptionTest

Это часть вывода javap:

public static final scala.None$ x();
  Signature: ()Lscala/None$;
  Code:
   0:   getstatic  #11; //Field OptionTest$.MODULE$:LOptionTest$;
   3:   invokevirtual  #55; //Method OptionTest$.x:()Lscala/None$;
   6:   areturn

Я также запускал javap на scala.None и scala.Option.Как из вывода javap можно понять, что:

  1. None является единственным объектом типа None.type, который расширяет Option
  2. Метод apply()для сопутствующего объекта требуется

?

Ответы [ 2 ]

71 голосов
/ 19 февраля 2012

Существуют правила, как код Scala компилируется в байт-код JVM.Из-за возможных конфликтов имен сгенерированный код не всегда интуитивно понятен, но если правила известны, можно получить доступ к скомпилированному коду Scala в Java.

Внимание: * 1005При написании этого я заметил, что javac и eclipse-javac ведут себя по-разному при доступе к коду Scala из Java.Возможно, что приведенный ниже код скомпилирован с одним из них, но не с другим.

Классы, конструкторы, методы

Здесь нет специальных правил.Следующий класс Scala

class X(i: Int) {
  def m1 = i*2
  def m2(a: Int)(b: Int) = a*b
  def m3(a: Int)(implicit b: Int) = a*b
}

доступен как обычный класс Java.Он компилируется в файл с именем X.class:

X x = new X(7);
x.m1();
x.m2(3, 5);
x.m3(3, 5);

Обратите внимание, что для методов без списка параметров создается пустой список параметров.Несколько списков параметров объединяются в один.

Поля, Значения

Для класса class X(var i: Int) Созданы методы получения и установки.Для класса class X(val i: Int) создается только Getter:

//Scala
val x = new X(5)
x.i = 3 // Setter
x.i // Getter

//Java
X x = new X(5);
x.i_$eq(3); // Setter
x.i(); // Getter

Обратите внимание, что в Java идентификатор не может включать специальные знаки.Поэтому скаляр генерирует для каждого из этих особых признаков определенное имя.Существует класс scala.reflect.NameTransformer , который может кодировать / декодировать операции:

scala> import scala.reflect.NameTransformer._
import scala.reflect.NameTransformer._

scala> val ops = "~=<>!#%^&|*/+-:\\?@"
ops: String = ~=<>!#%^&|*/+-:\?@

scala> ops map { o => o -> encode(o.toString) } foreach println
(~,$tilde)
(=,$eq)
(<,$less)
(>,$greater)
(!,$bang)
(#,$hash)
(%,$percent)
(^,$up)
(&,$amp)
(|,$bar)
(*,$times)
(/,$div)
(+,$plus)
(-,$minus)
(:,$colon)
(\,$bslash)
(?,$qmark)
(@,$at)

Класс class X { var i = 5 } транслируется по той же схеме, что и при создании поля вконструкторПрямой доступ к переменной i из Java невозможен, поскольку она является закрытой.

Objects

В Java нет такого понятия, как объект Scala.Поэтому скаляр должен делать немного магии.Для объекта object X { val i = 5 } создаются два файла класса JVM: X.class и X$.class.Первый работает как интерфейс, он включает статические методы для доступа к полям и методам объекта Scala.Последний является одноэлементным классом, который не может быть создан.У него есть Поле, которое содержит экземпляр класса singleton, названный MODULE$, который разрешает доступ к элементу singleton:

X.i();
X$.MODULE$.i();

Классы Case

Компилятор Scala автоматически генерирует application-метод для класса case и метод Getters для полей.Класс case case class X(i: Int) легко доступен:

new X(3).i();
X$.MODULE$.apply(3);

Черты

Черта trait T { def m }, которая содержит только абстрактные члены, компилируется в интерфейс, который помещается в классфайлы с именем T.class.Поэтому его легко реализовать с помощью Java-класса:

class X implements T {
  public void m() {
    // do stuff here
  }
}

Если черта содержит конкретные члены, генерируется файл класса с именем <trait_name>$class.class, в дополнение к обычному интерфейсу.Черта

trait T {
  def m1
  def m2 = 5
}

также может быть легко реализована в Java.Файл класса T$class.class содержит конкретные элементы свойства, но, похоже, к ним невозможно получить доступ из Java.Ни javac, ни eclipse-javac не скомпилируют доступ к этому классу.

Более подробную информацию о том, как компилируются признаки, можно найти здесь .

Функции

Литералы функций компилируются как анонимные экземпляры классов FunctionN.Объект Scala

object X {
  val f: Int => Int = i => i*2
  def g: Int => Int = i => i*2
  def h: Int => Int => Int = a => b => a*b
  def i: Int => Int => Int = a => {
    def j: Int => Int = b => a*b
    j
  }
}

компилируется в обычные файлы классов, как описано выше.Кроме того, каждый литерал функции получает свой собственный файл класса.Таким образом, для значений функции создается файл класса с именем <class_name>$$anonfun$<N>.class, где N - непрерывное число.Для методов функций (методов, которые возвращают функцию) генерируется файл класса с именем <class_name>$$anonfun$<method_name>$<N>.class.Части имени функции разделены знаками доллара, а перед идентификатором anonfun есть также два знака доллара.Для вложенных функций имя вложенной функции добавляется к внешней функции, это означает, что внутренняя функция получит файл класса, такой как <class_name>$$anonfun$<outer_method_name>$<N>$$anonfun$<inner_method_name>$<N>.class.Когда внутренняя функция не имеет имени, как видно из h, она получает имя apply.

Это означает, что в нашем случае мы получим:

  • X$$anonfun$1.classдля f
  • X$$anonfun$g$1.class для g
  • X$$anonfun$h$1$$anonfun$apply$1.class для h
  • X$$anonfun$i$1.class и X$$anonfun$i$1$$anonfun$j$1$1.class для i и j

Чтобы получить к ним доступ, используйте метод apply:

X.f().apply(7);
X.g().apply(7);
X.h().apply(3).apply(5);
X.i().apply(3).apply(5);

Ответить на вопрос

Вы должны знать:

  • нормальный класс Scala, к которому могут обращаться их конструкторы или их методы apply
  • когда нет конструктора, чем метод apply
  • когда нет конструктора и метода применения, чем другой файл класса, названный так же, как и класс, который добавляет знак доллара в конце. Искать в этом классе поле MODULE$
  • конструкторы и apply-методы наследуются, поэтому ищите суперклассы, если вы ничего не можете найти в подклассах

Некоторые примеры

Опция

// javap scala.Option
public abstract class scala.Option extends java.lang.Object implements ... {
  ...
  public static final scala.Option apply(java.lang.Object);
  public scala.Option();
}

javap говорит, что у него есть конструктор и метод apply. Кроме того, он говорит, что класс абстрактный. Таким образом, может использоваться только метод apply:

Option.apply(3);

Некоторые

// javap scala.Some
public final class scala.Some extends scala.Option implements ... {
  ...
  public scala.Some(java.lang.Object);
}

У него есть конструктор и метод apply (потому что мы знаем, что у Option есть один, а у некоторых расширяется Option). Используйте один из них и будьте счастливы:

new Some<Integer>(3);
Some.apply(3);

нет

// javap scala.None
public final class scala.None extends java.lang.Object{
  ...
}

Он не имеет конструктора, метода apply и не расширяет Option. Итак, мы посмотрим на None$:

// javap -private scala.None$
public final class scala.None$ extends scala.Option implements ... {
  ...
  public static final scala.None$ MODULE$;
  private scala.None$();
}

Да! Мы нашли поле MODULE$ и метод apply из Option. Кроме того, мы нашли приватный конструктор:

None$.apply(3) // returns Some(3). Please use the apply-method of Option instead
None$.MODULE$.isDefined(); // returns false
new None$(); // compiler error. constructor not visible

Список

scala.collection.immutable.List является абстрактным, поэтому мы должны использовать scala.collection.immutable.List$. У него есть метод apply, который ожидает scala.collection.Seq. Итак, чтобы получить список, нам нужно сначала Seq. Но если мы посмотрим на Seq, то там нет метода apply. Кроме того, когда мы смотрим на суперклассы Seq и scala.collection.Seq$, мы можем найти только методы apply, которые ожидают Seq. Итак, что делать?

Мы должны посмотреть, как scalac создает экземпляр List или Seq. Сначала создайте класс Scala:

class X {
  val xs = List(1, 2, 3)
}

Скомпилируйте его с помощью scalac и посмотрите на файл класса с помощью javap:

// javap -c -private X
public class X extends java.lang.Object implements scala.ScalaObject{
...
public X();
  Code:
   0:   aload_0
   1:   invokespecial   #20; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   getstatic   #26; //Field scala/collection/immutable/List$.MODULE$:Lscala/collection/immutable/List$;
   8:   getstatic   #31; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   11:  iconst_3
   12:  newarray int
   14:  dup
   15:  iconst_0
   16:  iconst_1
   17:  iastore
   18:  dup
   19:  iconst_1
   20:  iconst_2
   21:  iastore
   22:  dup
   23:  iconst_2
   24:  iconst_3
   25:  iastore
   26:  invokevirtual   #35; //Method scala/Predef$.wrapIntArray:([I)Lscala/collection/mutable/WrappedArray;
   29:  invokevirtual   #39; //Method scala/collection/immutable/List$.apply:(Lscala/collection/Seq;)Lscala/collection/immutable/List;
   32:  putfield    #13; //Field xs:Lscala/collection/immutable/List;
   35:  return

}

Конструктор интересный. Это говорит нам о том, что создан массив целых чисел (л. 12), который заполнен 1, 2 и 3. (л. 14-25). После этого этот массив доставляется до scala.Predef$.wrapIntArray (л. 26). В результате scala.collection.mutable.WrappedArray снова доставляется в наш Список (л. 29). В конце список хранится в поле (л. 32). Когда мы хотим создать список в Java, мы должны сделать то же самое:

int[] arr = { 1, 2, 3 };
WrappedArray<Object> warr = Predef$.MODULE$.wrapIntArray(arr);
List$.MODULE$.apply(warr);

// or shorter
List$.MODULE$.apply(Predef$.MODULE$.wrapIntArray(new int[] { 1, 2, 3 }));

Это выглядит ужасно, но работает. Если вы создадите красивую библиотеку, которая обернет доступ к библиотеке Scala, будет легко использовать Scala из Java.

Основная информация

Я знаю, что есть еще несколько правил, как код Scala компилируется в байт-код. Но я думаю, что с помощью приведенной выше информации можно найти эти правила самостоятельно.

15 голосов
/ 24 февраля 2012

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

scala> :paste
// Entering paste mode (ctrl-D to finish)

object OptionTest extends App {
  val x = scala.None
  val y = scala.Some("asdf")
}

// Exiting paste mode, now interpreting.

defined module OptionTest

scala> :javap -v OptionTest$
Compiled from "<console>"
public final class OptionTest$ extends java.lang.Object implements scala.App,scala.ScalaObject
  SourceFile: "<console>"
  Scala: length = 0x

  [lots of output etc]   

  public scala.None$ x();
    Code:
     Stack=1, Locals=1, Args_size=1
     0: aload_0
     1: getfield    #65; //Field x:Lscala/None$;
     4: areturn
...