Играя със 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 спедитор, вграденият също следва през двата препращача и поставя тялото на метода на чертата.