Как узнать об использовании Scala.none от java с использованием javap?

StackOverflow https://stackoverflow.com/questions/9350528

  •  27-10-2019
  •  | 
  •  

Вопрос

В предыдущем вопросе, Доступ к 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() Требуется метод для компаньона

?

Это было полезно?

Решение

Существуют правила, как код Scala составлен в JVM-ByteCode. Из -за потенциальных столкновений имени сгенерированный код не всегда интуитивно понятно, но если правила известны, можно получить доступ к скомпилированному коду 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) Создается только 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 идентификатор не разрешается включать специальные знаки. Поэтому Scalac генерирует для каждого из этих специальных признаков конкретное имя. Есть класс Scala.Reflect.Nametransformer который может кодировать/декодировать OPS:

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 автоматически генерирует Method Apply для класса корпуса и полученные для поля. Класс дела 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 не будут скомпилировать доступ к этому классу.

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

Функции

Функциональные литералы составлены в виде анонимных экземпляров функциональных классов. Объект 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

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

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

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

Ты должен знать:

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

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

Вариант

// 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 говорит, что у него есть конструктор и метод применения. Кроме того, он говорит, что класс абстрактный. Таким образом, только применимый метод может использовать:

Option.apply(3);

Немного

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

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

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

Никто

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

У него нет конструктора, нет применения-метода и не расширяет опцию. Итак, мы посмотрим на 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$. Анкет У него есть примененный метод, который ожидает scala.collection.Seq. Анкет Итак, чтобы получить список, нам нужен сначала SEQ. Но если мы посмотрим на SEQ, не будет Method Apply. Кроме того, когда мы смотрим на суперклассы SEQ и на scala.collection.Seq$ Мы можем найти только методы применения, которые ожидают SEQ. Так что делать?

Мы должны взглянуть, как Scalac создает экземпляр списка или 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

}

Конструктор интересен. Он говорит нам, что создается массив INT (л. 12), который заполнен 1, 2 и 3. (l. 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 составлен в Bytecode. Но я думаю, что с информацией выше должно быть возможно найти эти правила самостоятельно.

Другие советы

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

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top