Прямые ссылки - почему этот код компилируется?
Рассмотрим этот фрагмент:
object A {
val b = c
val c = "foo"
}
println( A.b ) // prints "null"
Как часть более крупной программы, это приведет к сбою во время выполнения. Компилятор, по-видимому, разрешает прямое обращение от "b" к (неинициализированному) "c", но "b" остается с исходным нулевым значением. Почему это разрешено? Существуют ли сценарии программирования, которые выиграют от этой функции?
Измените код на прямую последовательность и измените поведение:
val b = c
val c = "foo"
println( b ) // prints "foo"
Почему поведение отличается? И почему это даже работает? Спасибо.
Обновление 1:
Возник вопрос: как я запустил второй пример. Я немного упростил настройку и скомпилировал ее с помощью Scala 2.9.0.1 внутри IntelliJ IDEA 10.5.2 с последним плагином Scala. Вот точный код в недавно созданном и в противном случае пустом проекте, который я использую для проверки этого, который компилируется и работает отлично в этой среде:
package test
object Main {
def main( args: Array[String] ) {
val b = c
val c = "foo"
println( b ) // prints "foo"
}
}
Для чего это стоит, IDEA также думает (когда я нажимаю "через" ссылку на "c" в val b = c), я имею в виду (позднее) объявление 'c'.
Ответы
Ответ 1
Тело класса или объекта является основным конструктором. Конструктор, подобно методу, представляет собой последовательность операторов, которые выполняются по порядку - чтобы сделать что-либо еще, это должен быть совсем другой язык. Я уверен, что вам не хотелось бы, чтобы Scala выполнял инструкции ваших методов в любом другом порядке, чем последовательный.
Проблема заключается в том, что тело классов и объектов также является объявлением членов, и это является источником вашей путаницы. Вы видите объявления val
как таковые: декларативная форма программирования, например, программа Prolog или файл конфигурации XML. Но они действительно две вещи:
// This is the declarative part
object A {
val b
val c
}
// This is the constructor part
object A {
b = c
c = "foo"
}
Другая часть вашей проблемы заключается в том, что ваш пример очень прост. Это особый случай, когда определенное поведение, кажется, имеет смысл. Но рассмотрите что-то вроде:
abstract class A {
def c: String
}
class B extends A {
val b = c
override val c = "foo"
}
class C extends { override val c = "foobar" } with B
val x = new C
println(x.b)
println(x.c)
Чего вы ожидаете? Семантика выполнения конструктора гарантирует две вещи:
- Предсказуемость. Сначала вы можете обнаружить его неинтуитивным, но правила понятны и относительно просты в использовании.
- Подклассы могут зависеть от инициализации суперклассов (и, следовательно, его доступных методов).
Ответ 2
Это из-за устаревшей версии Scala.
С Scala 2.11.5 он скомпилирован с предупреждением или вообще не скомпилирован:
C:\Users\Andriy\Projects\com\github\plokhotnyuk>scala
Welcome to Scala version 2.11.5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_31).
Type in expressions to have them evaluated.
Type :help for more information.
scala> { object A { val b = c; val c = "foo" }; println(A.b) }
<console>:9: warning: Reference to uninitialized value c
{ object A { val b = c; val c = "foo" }; println(A.b) }
^
null
scala> { val b = c; val c = "foo"; println(A.b) }
<console>:9: error: forward reference extends over definition of value b
{ val b = c; val c = "foo"; println(A.b) }
^