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