Защо се създава анонимен клас при смесване на черта?

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

Причината за създаването на анонимен клас е, че обектът трябва да има реализации на всички методи от 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

Забележете, че компилаторът всъщност може да пренапише сайтовете за извикване на foo, за да извика статичните методи директно от сайта за извикване, а не чрез мостов метод. Причината, поради която не е направено по този начин, е, че тогава вече няма да поддържа подтипов полиморфизъм.

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
Той ще създаде нов клас само за всяко появяване на това във вашия код, а не за всеки екземпляр на този клас. Така че освен ако нямате new A with B в стотици кодови редове, което е много малко вероятно, не би трябвало да има проблем. - person drexin; 29.06.2012
comment
@John - Не знам. Аз лично вече бих имал клас при 10 случая; това са 10 различни места, където A with B се инстанцира (както @drexin правилно казва). Нямам често такива случаи. - person 0__; 29.06.2012