Как Scala черты, скомпилированные в байт-код Java?
Я играл с Scala некоторое время, и я знаю, что черты могут действовать как эквивалент Scala обоих интерфейсов и абстрактных классов. Как точно черты, скомпилированные в байт-код Java?
Я нашел несколько коротких объяснений, в которых сформулированные черты компилируются точно так же, как Java-интерфейсы, когда это возможно, и в противном случае взаимодействует с дополнительным классом. Тем не менее я до сих пор не понимаю, как Scala достигает линеаризации класса, недоступной в Java.
Есть ли хороший источник, объясняющий, как черты компилируются в байт-код Java?
Ответы
Ответ 1
Я не эксперт, но вот мое понимание:
Черты компилируются в интерфейс и соответствующий класс.
trait Foo {
def bar = { println("bar!") }
}
становится эквивалентом...
public interface Foo {
public void bar();
}
public class Foo$class {
public static void bar(Foo self) { println("bar!"); }
}
Из-за чего возникает вопрос: как вызывается метод статического бара в Foo $class? Эта магия выполняется компилятором в классе, в который помечена черта Foo.
class Baz extends Foo
становится чем-то вроде...
public class Baz implements Foo {
public void bar() { Foo$class.bar(this); }
}
Линеаризация классов просто реализует соответствующую версию метода (вызов статического метода в классе класса Xxxx $) в соответствии с правилами линеаризации, определенными в спецификации языка.
Ответ 2
Для обсуждения рассмотрим следующий пример Scala, используя несколько признаков с абстрактными и конкретными методами:
trait A {
def foo(i: Int) = ???
def abstractBar(i: Int): Int
}
trait B {
def baz(i: Int) = ???
}
class C extends A with B {
override def abstractBar(i: Int) = ???
}
В настоящий момент (т.е. от Scala 2.11) один признак кодируется как:
- an
interface
, содержащий абстрактные объявления для всех методов признаков (как абстрактных, так и конкретных)
- абстрактный статический класс, содержащий статические методы для всех конкретных методов, используя дополнительный параметр
$this
(в более старых версиях Scala этот класс не был абстрактным, но не имеет смысла его создавать )
- в каждой точке иерархии наследования, где смешанный признак, синтетические методы пересылки для всех конкретных методов в признаке, которые передаются статическим методам статического класса
Основным преимуществом этой кодировки является то, что черта без конкретных членов (которая изоморфна интерфейсу) фактически скомпилирована для интерфейса.
interface A {
int foo(int i);
int abstractBar(int i);
}
abstract class A$class {
static void $init$(A $this) {}
static int foo(A $this, int i) { return ???; }
}
interface B {
int baz(int i);
}
abstract class B$class {
static void $init$(B $this) {}
static int baz(B $this, int i) { return ???; }
}
class C implements A, B {
public C() {
A$class.$init$(this);
B$class.$init$(this);
}
@Override public int baz(int i) { return B$class.baz(this, i); }
@Override public int foo(int i) { return A$class.foo(this, i); }
@Override public int abstractBar(int i) { return ???; }
}
Однако для Scala 2.12 требуется Java 8, и, следовательно, он может использовать методы по умолчанию и статические методы в интерфейсах, и результат выглядит примерно так:
interface A {
static void $init$(A $this) {}
static int foo$(A $this, int i) { return ???; }
default int foo(int i) { return A.foo$(this, i); };
int abstractBar(int i);
}
interface B {
static void $init$(B $this) {}
static int baz$(B $this, int i) { return ???; }
default int baz(int i) { return B.baz$(this, i); }
}
class C implements A, B {
public C() {
A.$init$(this);
B.$init$(this);
}
@Override public int abstractBar(int i) { return ???; }
}
Как вы можете видеть, старый проект со статическими методами и форвардерами был сохранен, они просто складываются в интерфейс. Методы конкретного метода теперь были перенесены в интерфейс как методы static
, методы пересылки не синтезируются в каждом классе, но определены как методы default
, а статический метод $init$
(который представляет собой код в тело признака) также перемещается в интерфейс, что делает ненужным статический класс компаньона.
Возможно, это может быть упрощено следующим образом:
interface A {
static void $init$(A $this) {}
default int foo(int i) { return ???; };
int abstractBar(int i);
}
interface B {
static void $init$(B $this) {}
default int baz(int i) { return ???; }
}
class C implements A, B {
public C() {
A.$init$(this);
B.$init$(this);
}
@Override public int abstractBar(int i) { return ???; }
}
Я не уверен, почему это не было сделано. На первый взгляд, текущая кодировка может дать нам немного вперед-совместимость: вы можете использовать черты, скомпилированные с новым компилятором, с классами, скомпилированными старым компилятором, эти старые классы просто переопределяют методы default
пересылки, которые они наследуют от интерфейс с идентичными. Кроме того, методы форвардера будут пытаться вызвать статические методы на A$class
и B$class
, которые больше не существуют, так что гипотетическая переадресация на самом деле не работает.
Ответ 3
Очень хорошее объяснение этого:
Занятое руководство разработчика Java по Scala: О чертах и поведении - Черты в JVM
Цитата:
В этом случае он [компилятор] удаляет реализации метода и декларации полей, определенные в признаке, в класс, реализующий признак
Ответ 4
В контексте Scala 12 и Java 8 вы можете увидеть другое объяснение в commit 8020cd6:
Улучшенная поддержка inliner для кодировки с кодировкой 2.12
Некоторые изменения в кодировке признака наступили в конце цикла 2.12, и Inliner не был адаптирован для его наилучшего обеспечения.
В 2.12.0 конкретные методы признака кодируются как
interface T {
default int m() { return 1 }
static int m$(T $this) { <invokespecial $this.m()> }
}
class C implements T {
public int m() { return T.m$(this) }
}
Если для вложения выбран метод признака, то 2.12.0 inliner будет скопируйте его тело в статический супер-аксессор T.m$
, а оттуда в отправитель mixin C.m
.
Это фиксирует специальные случаи inliner:
- Мы не встраиваем в статические супер-аксессоры и пересылки mixin.
- Вместо этого при встраивании вызова пересылателя mixin, inliner также следует через два форвардера и строит тело метода признаков.