2015-06-12 3 views
3

В Scala, поля и методы принадлежат к одному и тому же пространству имен, поэтому поле может легко переопределить метод. Это очень привлекательная функция Scala. Например, это позволяет фиксировать метод класса к конкретной ссылкой на какой-то момент иерархии классов:Переопределение абстрактных методов со значениями, хорошая практика?

abstract class AC { def x: Int } 
class C extends AC { override val x = 10 } 

Предположим, что существует второй Base «с атрибутом вычисляется форма x:

abstract class AC { def x: Int; val y: Int = x*2 } 
class C extends AC { override val x = 10 } 

val v = (new C).y 

Можно было бы ожидаю v значение будет 20 но 0. Хорошо, происходит то, что конструктор AC вызывается до того, как C, следовательно, x (который теперь является атрибутом) еще не установлен в этой точке, а конструктор использует значение по умолчанию для целых чисел (может быть, неправильно).

Что меня поражает то, что с:

class C extends AC { override def x = 10 } 

val v = (new C).y 

v равна 20.

У меня есть подозрение, что разница исходит из того факта, что методы разрешаются иначе, чем значения. Может ли кто-нибудь объяснить разницу в поведении? подробнее о том, как работают конструкторы Scala? Не было бы более согласованным иметь одинаковое поведение в обоих случаях?

Относительно названия вопроса: Не опасно ли переопределять методы как значения? Некоторый клиентский код мог бы сделать это для класса библиотеки, не заметив, что зависимые атрибуты могут получить недопустимые значения, тем самым генерируя ошибки.

+1

Я думаю, что лучшей практикой было бы сделать 'y' ленивым или def, когда он ссылается на то, что может быть отменено. Хотя я не уверен, почему вторая инициализация имеет проблемы. Трудно найти что-либо в спецификации о том, как обрабатывать def с помощью val. –

ответ

2

Поля суперкласса создаются перед полями подкласса. Поэтому, учитывая

abstract class AC { 
    def x: Int 
    val y: Int = x * 2 
} 

class C extends AC { 
    override val x = 10 
} 

new C сначала создать экземпляр поля AC, в этом случае y, а затем поля C, т.е. x. Поскольку значения Int перед инициализацией составляют 0, y = x * 2 будет 0.

Теперь, если вы переопределить x как метод (def), то C имеет никаких полей не должны быть инициализированы и y = x * 2 вызывает метод x который возвращает 10, следовательно y принимает значение 20. Обновление: Чтобы сделать это ясно, методы не принадлежат к одному экземпляру, но распределяются между всеми экземплярами одного и того же класса. Следовательно, метод x создается во время компиляции не тогда, когда создается экземпляр C.

Вещь, которую я считаю опасной, заключается в том, что y в AC является val; это приводит к созданию экземпляра y до того, как поля подклассов инициализируются, образуя потенциальную прямую ссылку.Если вы хотите воздержаться от реализации y в качестве метода, вы можете реализовать его как lazy val, то есть он оценивается ровно один раз, а именно при его первом использовании, в этом случае только после полного экземпляра объекта.

abstract class AC { 
    def x: Int 
    lazy val y: Int = x * 2 
} 

class C extends AC { 
    override val x = 10 
} 

println((new C).y) // prints 20, because y is initialized on its first use 

Кстати: Та же проблема возникает, если переопределить val как val.

class AB { 
    val x = 10 
    val y = x * 2 
} 
class B extends AB { 
    override val x = 4 
} 

println((new B).y) // prints 0, because the overridden `x` is not yet initialized 
+0

Как описано в моем вопросе, я ожидаю, что 'y' будет 0, когда' x' является атрибутом; Меня удивляет то, что 'y' не является 0, когда' x' является методом. Как я понимаю из вашего ответа, когда 'y' создается экземпляром' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '', будучи его реализацией предоставлен подкласса. –

+0

Ваше редактирование говорит мне, что я могу быть misexpressing: Конечно, методы не принадлежат к одному экземпляру, здесь не вопрос. Это то, что, насколько я понимаю, факт 'y' получения правильного значения, когда' x' является методом, может быть объяснено тем, что конструктор 'AC' вызывает функцию' C' (CLASS) 'x'. –

+0

@ PabloFranciscoPérezHidalgo Да, когда вызывается метод 'x', правильная реализация выбирается на основе типа времени выполнения, здесь это экземпляр' C'. Хотя пример для C++, страница Wikipedia на [VTable] (https://en.wikipedia.org/wiki/Virtual_method_table) имеет приятный пример возможного расположения памяти. Я полагаю, что общая идея верна и для Scala. Также обратите внимание, что Scala создает getter-метод для каждого 'val', делая переопределение' def' как 'val' возможным. Отвечает ли это на вопрос? –

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