Прямые ссылки - почему этот код компилируется? - PullRequest
27 голосов
/ 14 октября 2011

Рассмотрим этот фрагмент:

 object A {
     val b = c
     val c = "foo"
 }
 println( A.b )   // prints "null"

В рамках более крупной программы это может привести к сбою во время выполнения. Компилятор, по-видимому, разрешает прямую ссылку из 'b' в (неинициализированную) 'c', но в 'b' остается исходное нулевое значение c. Почему это разрешено? Существуют ли сценарии программирования, которые выиграют от этой функции?

Измените код на прямую последовательность, и поведение изменится:

 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».

Ответы [ 2 ]

17 голосов
/ 14 октября 2011

Тело класса или объекта является основным конструктором. Конструктор, как и метод, представляет собой последовательность операторов, которые выполняются по порядку - чтобы сделать что-то еще, это должен быть совсем другой язык. Я уверен, что вы не хотели бы, чтобы 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)

Что вы ожидаете случиться? Семантика выполнения конструктора гарантирует две вещи:

  1. Предсказуемость. Поначалу вам может показаться, что это не интуитивно понятно, но правила ясны и относительно просты для следования.
  2. Подклассы могут зависеть от инициализации суперклассов (и, следовательно, их методов).

Выход:

дважды напечатает «foobar» => https://docs.scala -lang.org / tutorials / FAQ / initialization-order.html

1 голос
/ 26 февраля 2015

Это из-за устаревшей версии 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) }
                        ^
...