Ответы:
Я могу думать о двух различиях
В Scala есть раздел «Программирование, черта или нет?» который решает этот вопрос. Так как 1-е издание доступно в Интернете, я надеюсь, что все в порядке, чтобы процитировать все это здесь. (Любой серьезный программист Scala должен купить книгу):
Всякий раз, когда вы реализуете повторно используемую коллекцию поведения, вам придется решить, хотите ли вы использовать черту или абстрактный класс. Не существует твердого правила, но этот раздел содержит несколько рекомендаций для рассмотрения.
Если поведение не будет использоваться повторно , сделайте его конкретным классом. Это не многоразовое поведение в конце концов.
Если это может быть повторно использовано в нескольких, не связанных между собой классах , сделайте это признаком. Только черты могут быть смешаны в разных частях иерархии классов.
Если вы хотите наследовать от него в коде Java , используйте абстрактный класс. Поскольку признаки с кодом не имеют близкого аналога Java, обычно бывает неудобно наследовать признаки в классе Java. В то же время наследование от класса Scala точно такое же, как наследование от класса Java. В качестве одного исключения, черта Scala, содержащая только абстрактные элементы, транслируется непосредственно в интерфейс Java, поэтому вы можете свободно определять такие черты, даже если ожидаете, что код Java будет наследоваться от него. См. Главу 29 для получения дополнительной информации о совместной работе с Java и Scala.
Если вы планируете распространять его в скомпилированной форме и ожидаете, что внешние группы будут писать классы, унаследованные от него, вы можете склоняться к использованию абстрактного класса. Проблема в том, что когда признак получает или теряет член, любые классы, которые наследуют его, должны быть перекомпилированы, даже если они не изменились. Если внешние клиенты будут вызывать только поведение, а не наследовать его, тогда использовать черту вполне нормально.
Если эффективность очень важна , склоняйтесь к использованию класса. Большинство сред выполнения Java делают вызов виртуального метода членом класса более быстрой операцией, чем вызов метода интерфейса. Черты компилируются в интерфейсы и, следовательно, могут немного снизить производительность. Однако этот выбор следует делать только в том случае, если вы знаете, что рассматриваемая черта представляет собой узкое место в производительности, и у вас есть доказательства того, что использование класса вместо этого фактически решает проблему.
Если вы все еще не знаете , после рассмотрения вышеизложенного, начните с того, что сделайте это чертой. Вы всегда можете изменить его позже, и в целом использование черты оставляет больше возможностей открытым.
Как упоминал @Mushtaq Ahmed, в признаке не может быть никаких параметров, передаваемых первичному конструктору класса.
Другое отличие заключается в лечении super
.
Другое различие между классами и признаками состоит в том, что, хотя в классах
super
вызовы статически связаны, в признаках они динамически связаны. Если вы пишетеsuper.toString
в классе, вы точно знаете, какая реализация метода будет вызвана. Однако, когда вы пишете то же самое в признаке, реализация метода, вызываемая для супер-вызова, не определяется при определении признака.
См. Остальную часть главы 12 для более подробной информации.
Изменить 1 (2013):
Существует тонкое различие в поведении абстрактных классов по сравнению с чертами. Одно из правил линеаризации состоит в том, что оно сохраняет иерархию наследования классов, которая имеет тенденцию выдвигать абстрактные классы позднее в цепочке, в то время как признаки могут быть легко смешаны. В определенных обстоятельствах на самом деле предпочтительнее находиться в последней позиции линеаризации классов Таким образом, абстрактные классы могут быть использованы для этого. Смотрите линеаризацию ограничивающего класса (порядок смешивания) в Scala .
Изменить 2 (2018):
Начиная с Scala 2.12, поведение бинарной совместимости черты изменилось. До 2.12 добавление или удаление члена для признака требовало перекомпиляции всех классов, которые наследуют признак, даже если классы не изменились. Это связано с тем, как черты кодировались в JVM.
Начиная с Scala 2.12, черты компилируются в интерфейсы Java , поэтому требования немного ослабли. Если признак выполняет одно из следующих действий, его подклассы все еще требуют перекомпиляции:
- определение полей (
val
илиvar
константа в порядке -final val
без типа результата)- призвание
super
- оператор инициализатора в теле
- продление класса
- опираясь на линеаризацию, чтобы найти реализации в правильном супертрейте
Но если этого не происходит, вы можете обновить его, не нарушая бинарную совместимость.
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine
- Может кто-нибудь объяснить, в чем здесь разница? extends
против with
?
extends
и with
. Это чисто синтаксический. Если вы наследуете от нескольких шаблонов, первый получает extend
, все остальные получают with
, вот и все. Думайте о with
запятой class Foo extends Bar, Baz, Qux
.
Для чего бы это ни стоило, Odersky et al. Программирование в Scala рекомендует, когда вы сомневаетесь, использовать черты. Вы всегда можете изменить их на абстрактные классы позже, если это необходимо.
Помимо того факта, что вы не можете напрямую расширять несколько абстрактных классов, но вы можете смешивать несколько признаков в классе, стоит упомянуть, что признаки являются наращиваемыми, поскольку супер-вызовы в признаке динамически связаны (это относится к классу или признаку, смешанному до текущий).
Из ответа Томаса « Разница между абстрактным классом и чертой» :
trait A{
def a = 1
}
trait X extends A{
override def a = {
println("X")
super.a
}
}
trait Y extends A{
override def a = {
println("Y")
super.a
}
}
scala> val xy = new AnyRef with X with Y
xy: java.lang.Object with X with Y = $anon$1@6e9b6a
scala> xy.a
Y
X
res0: Int = 1
scala> val yx = new AnyRef with Y with X
yx: java.lang.Object with Y with X = $anon$1@188c838
scala> yx.a
X
Y
res1: Int = 1
В Программировании Scala авторы говорят, что абстрактные классы создают классические объектно-ориентированные отношения «есть», в то время как черты - это scala-способ композиции.
Абстрактные классы могут содержать поведение - они могут параметризоваться с помощью аргументов конструктора (что не может иметь признаков) и представлять работающий объект. Черты вместо этого просто представляют одну особенность, интерфейс одной функциональности.
trait Enumerable
с большим количеством вспомогательных функций, я бы не назвал их поведением, а просто функциональностью, связанной с одной функцией.