/ / Как се компилират чертите на Scala в байт код на Java? - скала, байт код

Как се натрупват Scala черти в Java байткод? - скала, байткод

Играя със Scala от известно време и знам, че чертите могат да действат като еквивалент на Scala както на интерфейсите, така и на абстрактните класове. Как точно се компилират черти в байт код на Java?

Намерих няколко кратки обяснения, които заявихачертите се компилират точно като Java интерфейси, когато е възможно, и интерфейси с допълнителен клас в противен случай. Все още не разбирам обаче как Scala постига линеаризация на клас, функция, която не се предлага в Java.

Има ли добър източник, обясняващ как чертите се компилират в байт кода на Java?

Отговори:

64 за отговор № 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 $? Тази магия се прави от компилатора в класа, в който се смесва чертата на Foo.

class Baz extends Foo

става нещо като ...

public class Baz implements Foo {
public void bar() { Foo$class.bar(this); }
}

Класическата линеаризация просто изпълняваподходяща версия на метода (извикване на статичния метод в класа Xxxx $ class) съгласно правилата за линеаризация, дефинирани в спецификацията на езика.


4 за отговор № 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) една черта се кодира като:

  • един 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 които вече не съществуват, така че хипотетичната напред-съвместимост всъщност не работи.


1 за отговор № 3

Много добро обяснение на това е в:

Заетото ръководство за разработчици на Java за Scala: От черти и поведения - черти в JVM

цитат:

В този случай [компилаторът] пуска реализациите на метода и декларациите на поле, дефинирани в чертата, в класа, който реализира чертата


0 за отговор № 4

В контекста на Scala 12 и Java 8 можете да видите друго обяснение в ангажирайте 8020cd6:

По-добра поддръжка за вграждане за кодиране на черти 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 би копирайте тялото му в статичния супер аксесоар T.m$, а от там и в спедиторът за смесване C.m.

Това ангажира специални случаи на вградения:

  • Ние не се вграждаме в статични супер аксесоари и смесителни препращачи.
  • Вместо това, когато вграждате извикване на mixin спедитор, вграденият също следва през двата препращача и поставя тялото на метода на чертата.