Как узнать об использовании Scala.none от java с использованием javap?
-
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
вывод этого:
None
является единственным объектомNone.type
Тип, который продлеваетсяOption
- А
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
для 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
Чтобы получить доступ к ним, используйте их применение-меток:
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