Как узнать об использовании scala.None из Java с использованием javap?

В предыдущем вопросе Доступ к 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 ответа

Решение

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

Внимание: при написании этой статьи я заметил, что 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) создается только геттер:

//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 не возможно, потому что это личное.

Объекты

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

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

Тематические классы

Компилятор Scala автоматически генерирует метод apply для класса case и метод Getters для полей. Класс дела 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 для г
  • X$$anonfun$h$1$$anonfun$apply$1.class для ч
  • X$$anonfun$i$1.class а также X$$anonfun$i$1$$anonfun$j$1$1.class для меня и 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$ поле и метод применения опции. Кроме того, мы нашли приватный конструктор:

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 компилируется в байт-код. Но я думаю, что с помощью приведенной выше информации можно найти эти правила самостоятельно.

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

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
Другие вопросы по тегам