Реализация абстрактного метода с чертой, несовместимым поведением компилятора? - PullRequest
6 голосов
/ 04 июля 2010

У меня есть базовый класс из библиотеки Java, код которого я не могу изменить.Этот класс (A) имеет пустой метод (b), который должен был быть объявлен как абстрактный:

class A {
  def b { }
}

Я расширяю этот класс в Scala и переопределяю метод, чтобы сделать его абстрактным:

abstract class AA extends A {
  override def b
}

Теперь я реализую этот метод в признаке:

trait B {
  def b { println("B") }
}

Если я расширяю AA с помощью признака B, я получаю ошибку: переопределение метода b в классе A типа => Unit;метод b в признаке B типа => Юнит нуждается в модификаторе override:

class C extends AA with B {}

Вместо этого, если код был таким, все компилируется без ошибок, что мне кажется немного противоречивым:

abstract class AA {
  def b
}

trait B {
  def b { println("B") }
}

class C extends AA with B {}

Я использую Scala 2.8.0RC3 и совершенно новый для языка (3 дня).Другое странное и связанное с этим поведение заключается в том, что метка переопределения не обязательна при создании b абстрактного:

abstract class AA extends A {
  def b
}

Ответы [ 3 ]

5 голосов
/ 04 июля 2010

Чтобы попытаться увидеть, что происходит, я попробовал это:

scala> class A{
     |   def b{ }
     | }
defined class A

scala> abstract class AA extends A{
     |   override def b
     | }
defined class AA

scala> class AAA extends AA{
     |   def b = println("AAA")
     | }
<console>:8: error: overriding method b in class A of type => Unit;
 method b needs `override' modifier
         def b = println("AAA")
             ^

Очевидно, источник проблемы в том, что абстрактные классы не могут "освободить" методы в своем суперклассе от необходимостиподклассы абстрактного класса для включения модификатора override.

2 голосов
/ 05 июля 2010

Проблема очень тонкая. Как правило, ваш класс AA , который расширяет A , должен быть смешан с чертами, которые также расширяют A .

Вы сделали:

class A {
  def b { }
}

abstract class AA extends A {
  override def b
}

trait B {
  def b { println("B") }
}

Следовательно, когда вы смешиваете AA и B , метод b определяется дважды. Один раз A (не переопределяется, поскольку определение в B заменяет переопределение в AA ), а второе - B , компилятор может не выбрал один над другим, потому что нет никакой иерархии между двумя (одинаково названными, но не связанными) методами. Если хотите, подумайте об этом так: компилятор «смешивает» тела AA и B ; если он выберет метод из AA , он будет абстрактным, если он выберет метод из B (что должно произойти), так как это не переопределение, вы застряли с двумя методами б .

Чтобы решить эту проблему, вы должны убедиться, что оба метода override одинаковы, и в этом случае компилятор поймет, что вы говорите о том же методе, и отдаст приоритет последней смешанной характеристике.

Теперь, чтобы переопределить метод b в B , этот класс также должен наследоваться от A . Таким образом, канонический способ сделать это будет:

class A {
  def b { }
}

abstract class AA extends A {
  override def b
}

trait B extends A{
  def b { println("B") }
}

class C extends AA with B {}

Что компилируется просто отлично.

Теперь, когда вы делаете:

abstract class AA {
  def b
}

trait B {
  def b { println("B") }
}

class C extends AA with B {}

ясно, что оба метода одинаковы, поэтому компилятор знает, что он должен использовать метод из черты.

Другие решения включают в себя:

  1. Сделать B переопределить AA
  2. Сделать b в A аннотацией (но вы этого не хотели)

Опять же, проблема очень тонкая, но, надеюсь, я немного прояснил ситуацию. Чтобы лучше понять, прочитайте Набор стековых черт Scala .

2 голосов
/ 04 июля 2010

Не уверен, что это правильное решение, но если ваш trait B расширяет A (и переопределяет b), то все прекрасно скомпилируется:

Сначала давайте определим A и AA как вы представляете их в своем вопросе:

C:\Users\VonC>scala
Welcome to Scala version 2.8.0.RC5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_18).
Type in expressions to have them evaluated.
Type :help for more information.

scala> class A {
     | def b { println("A b") }
     | }
defined class A

scala> new A
res5: A = A@153bedc4

scala> res5.b
A b

scala> abstract class AA extends A {
     | override def b
     | }
defined class AA

Что вы сделали:

scala> trait B {
     | override def b { println("B b") }
     | }
<console>:6: error: method b overrides nothing
       override def b { println("B b") }
                    ^

То, что я пробовал с чертой B (чтобы можно было добавить 'override'):

scala> trait B extends A {
     | override def b { println("B b") }
     | }
defined trait B

Итак:

scala> class C extends AA with B {}
defined class C

scala> new C
res7: C = C@1497b7b1

scala> res7.b
B b

Правильный b переопределенный метод вызывается с C.b


Что касается вашего очевидного«несоответствие», см. Scala для Java-беженцев. Часть 5. Черты и типы :

Начнем с того, что существует постоянно раздражающее ключевое слово override.Я уже упоминал в статье о базовом ООП, что любой метод, который переопределяет метод в суперклассе, должен быть объявлен с модификатором override.В то время я сравнил его с языком, предписывающим использовать аннотацию @Override, с его главной целью - внедрять передовую практику.

Реальный ключ к силе черт - это способ, которымкомпилятор обрабатывает их в наследующем классе.
Черты - это фактически миксины, а не истинные родительские классы .
Любые неабстрактные члены черты фактически включаются в класс наследования, как в физической частикласс.Ну, не физически, но вы получите картину.
Как будто компилятор выполняет вырезание и вставку с неабстрактными членами и вставляет их в наследующий класс.Это означает, что в пути наследования нет двусмысленности, что означает отсутствие проблемы с алмазом.

Так что вам не нужно ключевое слово override во втором примере.

...