2014-10-17 5 views
4

У меня есть следующий код в Скале:Доступ к членам Mixin черты

trait A { 
    val foo: String => Int = { in => 
    in.toInt 
    } 
} 

trait B extends A { 
    def bar(a: String) = { 
    foo(a) 
    } 
} 

class C(a: String) { 
    self: B => 

    val b = bar(a) 
} 

val x = new C("34") with B 

Во конкретизации x я получаю NPE. Не могу понять, почему.

редактировать

ПРИМЕЧАНИЕ: Не могу понять, почему foo из A признака не инициализируются

ответ

3

Обратитесь к http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html.

Единственным дополнением к этому является то, что self-type делает реферат класса C. Так на самом деле вы делаете:

abstract class C(a: String) { 
    def bar(a: String): Int 
    val b = bar(a) 
} 

val x = new C("34") with B 

Вы можете попробовать его заменить в коде и увидеть тот же результат. Дополнительная информация here.

Вкратце: линеаризация новых C с B будет (B < - A) < - C, поэтому инициализация C -> (A -> B). См. Class Initialization section раздел:

После выполнения конструктора суперкласса выполняются конструкторы для каждого признака mixin. Поскольку они выполняются в порядке справа налево в пределах линеаризации, но линеаризация создается путем изменения порядка признаков, это означает, что конструкторы для признаков mixin выполняются в том порядке, в котором они появляются в объявлении для класса , Помните, однако, что, когда mixins разделяет иерархию, порядок выполнения может быть не совсем таким же, как то, как микшины появляются в объявлении.

В вашем случае new C("34") with B равно class K extends C("34") with B; new K. Обратите внимание: сам тип класса C не влияет на порядок инициализации.

Упрощенный пример:

scala> trait C {def aa: String; println(s"C:$aa")} 
defined trait C 

scala> trait D {val aa = "aa"; println(s"D:$aa")} 
defined trait D 

scala> new C with D 
C:null 
D:aa 
res19: C with D = [email protected] 

Решение: Если Foo помещен в библиотеку третьей стороны (так что вы не можете сделать его ленивым), вы можете просто использовать само-тип смесь-в вместо или в наименьшая смесь А в класс С:

trait A { 
    val foo: String => Int = { in => 
    in.toInt 
    } 
} 

trait B extends A {  
    def bar(a: String) = { 
    foo(a) 
    } 
} 

class C(a: String) extends A { 
    self: B => 

    val b = bar(a) 
} 

val x = new C("34") with B 
+0

о классе абстрактности: это действительно так? есть ли у вас ссылка на спецификацию, которая доказывает это? Даже если это правда, я до сих пор не понимаю, почему это не работает: когда мы говорим «новый C (« 34 ») с B', это то же самое, что мы автоматически переопределяем метод« bar »в абстрактном C. Но мы переопределяем это с классом, который простирается от черты A, поэтому сначала необходимо инициализировать элементы признака A. – maks

+0

1) Вы можете начать чтение [здесь] (http://stackoverflow.com/questions/11274106/does-a-class-with-a-self-type-of-another-class-make-sense), но это в значительной степени очевидным - вы не можете создать экземпляр C без реализации метода bar. И эта реализация неизвестна для самого C, потому что она может быть переопределена во время создания экземпляра C – dk14

+0

2) Это не то же самое - признак B переопределяет только A, линеаризация по-прежнему B <- A <- C. Но инициализация - {C -> A - > B} (см. Раздел [Секция инициализации класса] (http://jim-mcbeath.blogspot.ro/2009/08/scala-class- linearization.html)). Таким образом, панель элементов не известна во время инициализации C. Другими словами, C и B <- A инициализируются независимо. – dk14

0

Объявить A.foo быть lazy val.

+0

это упрощенный пример. В действительности «черта А» находится в сторонней библиотеке. Но я хочу понять механику. Почему это происходит? – maks

1

Короткий ответ на то, почему вы получите NullPointerException в том, что инициализация C требует инициализации b, который вызывает метод, хранящийся в val foo, который не инициализирован в этой точке.

Вопрос, почему foo не инициализирован в этой точке? К сожалению, я не могу полностью ответить на этот вопрос, но я хотел бы показать вам некоторые эксперименты:

Если изменить подпись C к extends B, то B, как суперкласс C конкретизируется до того, что приводит к не исключение будучи брошенным.

В самом деле

trait A { 
    val initA = { 
    println("initializing A") 
    } 
} 

trait B extends A { 
    val initB = { 
    println("initializing B") 
    } 
} 

class C(a: String) { 
    self: B => // I imagine this as C has-a B 
    val initC = { 
    println("initializing C") 
    } 
} 

object Main { 
    def main(args: Array[String]): Unit ={ 
    val x = new C("34") with B 
    } 
} 

печатает

initializing C 
initializing A 
initializing B 

в то время как

trait A { 
    val initA = { 
    println("initializing A") 
    } 
} 

trait B extends A { 
    val initB = { 
    println("initializing B") 
    } 
} 

class C(a: String) extends B { // C is-a B: The constructor of B is invoked before 
    val initC = { 
    println("initializing C") 
    } 
} 

object Main { 
    def main(args: Array[String]): Unit ={ 
    val x = new C("34") with B 
    } 
} 

печатает

initializing A 
initializing B 
initializing C 

Как вы можете видеть, порядок инициализации отличается. Я полагаю, что инъекция зависимостей self: B => будет чем-то вроде динамического импорта (то есть помещение полей экземпляра B в объем C) с составом B (т. Е. C имеет -B). Я не могу доказать, что он решается именно так, но когда вы переходите к отладчику IntelliJ, поля B не перечислены под this, все еще находясь в области видимости.

Это должно ответить на вопрос о том, почему вы получаете NPE, но оставляет открытым вопрос о почему Mixin не первый экземпляр. Я не могу думать о проблемах, которые могут возникнуть в противном случае (так как расширение черты делает это в основном), поэтому это может быть либо выбор дизайна, либо никто не думал об этом случае использования. К счастью, это приведет только к проблемам во время создания экземпляра, поэтому лучшим «решением» является, вероятно, не использовать смешанные значения во время создания экземпляра (т. Е. Конструкторы и val/var членов).

Edit: Использование lazy val также хорошо, так что вы можете также определить lazy val initC = {initB}, потому что lazy val не выполняются, пока они не нужны. Однако, если вы не заботитесь о побочных эффектах или производительности, я бы предпочел def - lazy val, потому что за ним меньше «магии».

Смежные вопросы