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