Как узнать об использовании 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
вывод, что:
None
является единственным объектомNone.type
тип, который расширяетсяOption
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
для FX$$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