Почему при смешивании трейта создается анонимный класс?

scala> class A
defined class A

scala> trait B
defined trait B

Создание объекта класса A дает нам:

scala> new A
res4: A = A@11ea3fc

Но создание объекта класса A с добавлением признака B дает нам:

scala> new A with B
res3: A with B = $anon$1@172aa3f

Здесь у нас есть анонимный класс (на это указывает anon). Почему ?

Это потому, что тип A with B считается новым типом (и который ранее не был определен с помощью идентификатора)?


person John Threepwood    schedule 28.06.2012    source источник


Ответы (2)


Дело не только в том, что A with B следует рассматривать как новый тип. Для системы типов Scala не имеет прямого значения, существует ли класс, соответствующий A with B. Анонимный класс создается, потому что он должен содержать методы bridge для всех методов в смешанных трейтах.

Причина создания анонимного класса заключается в том, что объект должен иметь реализации всех методов из A и всех методов из B. На уровне байт-кода JVM это гарантирует наследование нескольких классов, а модель множественного наследования не поддерживается в JVM.

Чтобы имитировать множественное наследование (или композицию примесей, как бы вы это ни называли), Scala делает следующие вещи, когда вы создаете трейт:

  1. Если у трейта T нет реализаций методов, он создает интерфейс, который определяет все методы трейта.
  2. Если у типажа T есть реализации метода, он дополнительно создает класс T$class, который имеет статический метод для каждого из конкретных методов в T. Этот статический метод имеет то же тело, что и соответствующий метод в T, но его сигнатура изменена и включает параметр this. Если бы T имел:

    def foo(x: Int) = x
    

тогда T$class будет иметь:

<static> def foo($this: T, x: Int) = x

Класс, полученный путем примешивания композиции некоторого класса A и некоторого типажа T, будет затем иметь специальный сгенерированный метод моста, который перенаправляет вызов статическому методу, содержащему тело. Таким образом, тело метода не дублируется в каждом классе, который смешивается с T. Вот почему должен быть создан анонимный класс — в нем должны быть определены мостовые методы для каждого метода в T.

Вот пример. Когда вы создаете новый класс, выполняя миксиновую композицию, например. позвони new A with T:

class A {
  def bar = println("!")
}

trait T {
  def foo(x: Int) = x
}

new A with T

компилятор перепишет его примерно так:

class A {
  def bar = println("!")
}

<interface> T {
  def foo(x: Int): Int
}

class T$class {
  <static> def foo($this: T, x: Int) = x
}

class $anon extends A <implements> T {
  // notice that `bar` is inherited, but `foo` is not
  <bridge> def foo(x: Int) = T$class.foo(this, x)
}
new $anon

Обратите внимание, что компилятор может фактически переписать callsites на foo, чтобы вызывать статические методы непосредственно из callsite, а не через метод моста. Причина, по которой это не делается таким образом, заключается в том, что тогда он больше не будет поддерживать полиморфизм подтипов.

person axel22    schedule 28.06.2012
comment
Обратите внимание, что вызов scalac с аргументом -Xprint:mixin покажет точную конструкцию, которую создает Scala. -Xshow-phases показывает другие фазы, которые можно использовать с -Xprint:. - person outis; 11.01.2015

да. Хотя ваш тип по-прежнему A with B, должен существовать базовый класс Java, реализующий оба интерфейса. В этом нет ничего плохого, за исключением того, что если вы создаете объекты таким образом сотни раз, у вас, вероятно, будут сотни файлов классов. В таком случае вы можете создать выделенный class AB extends A with B, а затем создать экземпляр new AB.

В качестве примечания вы обнаружите, что вы также не можете напрямую создавать экземпляры признаков, например. new B не работает. Здесь вам также нужно создать явный класс, например. new B {}, что снова приводит к синтетическому («анонимному») классу.

person 0__    schedule 28.06.2012
comment
Спасибо. Каково будет количество анонимных объектов, если выделенный класс имеет больше смысла с точки зрения производительности? 10, 100, 1000 или больше? - person John Threepwood; 29.06.2012
comment
Он будет создавать новый класс только для каждого экземпляра this в вашем коде, а не для каждого экземпляра этого класса. Поэтому, если у вас нет new A with B в сотнях строк кода, что очень маловероятно, проблем быть не должно. - person drexin; 29.06.2012
comment
@Джон, я не знаю. Лично у меня был бы класс уже в 10 случаях; это 10 разных мест, где создается экземпляр A with B (как правильно говорит @drexin). У меня такие случаи не часто. - person 0__; 29.06.2012