От Java к Scala (4): особенности

Java задняя часть Scala

Эта статья была опубликована Rhyme вScalaCoolБлог команды.

Черты, черты, с которыми мы одновременно знакомы и не знакомы. Привычность заключается в том, что вы обнаружите, что он очень похож на интерфейс, который вы обычно используете в Java, а странность заключается в том, что новый игровой процесс Traits заставит вас сломать познание исходного интерфейса и войти в более сложные, более продвинутые области. играть. Итак, в начале мы можемTraitsЕсть предварительное понимание: это усиленная версияinterface. Позже, узнав об этом больше, вы обнаружите, что Traits больше похожи на классы, чем на интерфейсы Java. Позже вы можете понять, что,Черты пытаются лучше интегрировать абстракцию в единое целое.

Начало работы с чертами

Чтобы избежать дорогостоящих затрат на множественное наследование (конфликт методов или полей, алмазное наследование и т. д.) в Java, разработчики Java использовали интерфейсный интерфейс. И для того, чтобы решить Java интерфейс не может быть выполненоstackable modifications(то есть состояние объекта нельзя использовать для итерации), и поле не может быть предоставлено.В Scala мы используемTraitsЧерты, а не интерфейсы.

определить черту

trait Animal {
  val typeOf: String = "哺乳动物" //  带有默认值的字段

  def move(): Unit = {  // 带有默认实现的方法
    println("walk")
  }

  def eat() //未实现的抽象方法
}

Приведенный выше код аналогичен следующему коду Java.

public interface Animal {
    String typeOf = "哺乳动物";

    default void move() {
        System.out.println("walk");
    }

    void eat();
}

Использование ключевых слов в Scalatraitвместоinterface, как интерфейс Java,traitТакже могут быть реализации методов по умолчанию. То есть существуют Java-интерфейсы.traitВ принципе, они тоже есть, и реализовать их гораздо элегантнее. Причина, по которой мы говорим что-то похожее на приведенный выше код Java, заключается в том, чтоtraitимеет поляtypeOf,а такжеinterfaceимеет статические свойстваtypeOf. Этоinterfaceа такжеtraitнебольшая разница. Но присмотритесь и подумайте об этой разнице,Лучшее и более гибкое проектирование поля, делает ли этоtraitАбстракции лучше организованы, что делает их лучшим целым.

mix in trait

Как и Java, Scala поддерживает только одиночное наследование, но может иметь любое количество трейтов. В Scala мы не называем интерфейсimplementsдостигнуто, ноtraitsОн смешивается с классом путем смешивания.

class Bird extends Animal {
  override val typeOf: String = "蛋生动物"

  override def eat(): Unit = {
    println("eat bugs")
  }

  override def move(): Unit = {
    println("fly")
  }
}

В приведенном выше кодеBirdКлассы смешиваются в чертахAnimal. Когда класс смешивается с несколькими чертами, вам нужно использоватьwithключевые слова

trait Egg

class Bird extends Animal with Egg{
  override val typeOf: String = "蛋生动物"

  override def eat(): Unit = {
    println("eat bugs")
  }

  override def move(): Unit = {
    println("fly")
  }
}

В Scala мы быextends withЭтот синтаксис читается целиком, например, в приведенном выше коде мы будемextends Animal with Eggрассматривать в целом, а затемBirdмиксин класса. Ты чувствуешь это отсюда?traitПопытка лучше интегрировать абстракции в единое целое.

Здесь вы можете найти это по сравнению сJava interface,traitбольше похоже на классы. И это правда,traitМожет иметь все характеристики класса, кроме отсутствия параметров конструктора. с этой точки зренияtraitДля достижения того же эффекта можно использовать поля конструктора. То есть вы не можете передавать параметры трейтам так же, как вы передаете параметры конструктора классам. Конкретный код здесь демонстрироваться не будет.

На самом деле, здесь мы можем просто подумать о том, почему мы должныtraitразработан какclass, намеренное или непреднамеренное совпадение дизайнера. На самом деле, несмотря ни на что,Я лично думаю, но с точки зрения дизайна,classдизайн классаtraitПри большей согласованности объектами, созданными классом, можно хорошо управлять, почему бы нам не управлять нашими абстракциями так же, как мы управляем объектами?

Два основных применения черт

TraitsДва наиболее распространенных способа его использования: один похож на интерфейс Java для разработки многофункциональных интерфейсов, а другой уникален для Traits.stackable modifications. здесь сказаноinterfaceа такжеtraitВторое отличие, Traits поддерживаетstackable modificatio, что позволяет ему использовать состояние объекта, которое можно гибко повторять.

rich interface

Применение богатого интерфейса благодаряinterfaceПоддержка функции методов по умолчанию в , с одной стороны, ослабляет прочную связь между реализацией и реализованным между классами и интерфейсами, а с другой стороны, подменяет расширяемость программы большой гибкостью.traitПриложения в этом отношении мало чем отличаются от Java. а такжеtraitЗа реализацией метода по умолчанию вinterfaceсерединаdefaultМетод по умолчанию.

trait Hello {
  def hello(): Unit = {println("hello")
  }
}
interface Hello2 {
    default void hello() {...}
}

stackable modifications

оstackable modifications, как следует из названия, мы будемmodificationсохранен вstackв стеке. То есть мы можем итеративно обрабатывать результат операции для достижения желаемого результата. Это полезно для нужд, которые хотят распределить обработку и получить определенный результат.

Здесь мы беремprogramming in scalaпример в

abstract class IntQueue {
  def get(): Int

  def put(x: Int)
}

import scala.collection.mutable.ArrayBuffer

class BasicIntQueue extends IntQueue {
  private val buf = new ArrayBuffer[Int]

  def get() = buf.remove(0)

  def put(x: Int) {
    buf += x
  }
}

trait Doubling extends IntQueue {
  abstract override def put(x: Int) {
    super.put(2 * x)
  }
}

trait Incrementing extends IntQueue {
  abstract override def put(x: Int) {
    super.put(x + 1)
  }
}

trait Filtering extends IntQueue {
  abstract override def put(x: Int) {
    if (x >= 0) super.put(x)
  }
}

В приведенном выше коде мы определяем абстрактную очередь сputа такжеgetсоответствующий метод реализации предоставляется в классе BasicIntQueue. При этом определяются три характеристикиDoubling,Incrementing,Filtering, все они наследуют абстрактный класс IntQueue (помните, что я говорил ранее,traitМожет иметь все характеристики класса) и переопределять методы.Doublingобработает результат *2,IncrementingЧерта добавляет +1 к результату обработки,FilteringЗначения

Мы смотрим на следующие текущие результаты

scala> val queue = (new BasicIntQueue with Incrementing with Filtering)
queue: BasicIntQueue with Incrementing with Filtering...
scala> queue.put(-1); queue.put(0); queue.put(1)
scala> queue.get()
res15: Int = 1
scala> queue.get()
res16: Int = 2
scala> val queue = (new BasicIntQueue with Filtering with Incrementing)
queue: BasicIntQueue with Filtering with Incrementing...
scala> queue.put(-1); queue.put(0); queue.put(1)
scala> queue.get()
res17: Int = 0
scala> queue.get()
res18: Int = 1
scala> queue.get()
res19: Int = 2

Внимательно наблюдайте за приведенным выше кодом, поймите приведенный выше код, вы в основном пойметеstackable modifications.

Во-первых, вы можете заметить, что приведенные выше два фрагмента кода в целом похожи, но дают разные результаты только из-за идиосинкразии.Filteringа такжеIncrementingПорядок смешивания другой. Давайте внимательнее посмотрим на реализацию метода в трейте, и мы увидим, что в трейте все проходятsuperКлючевое слово вызывает метод родительского класса. И в этом причина описанной выше ситуации.traitсерединаsuperда поддержкаstackable modificationsфундаментальный ключ.

существуетtraitсерединаsuperдинамически связан, иsuperВызов метода в другом трейте, и то, какой метод в трейте вызывается, зависит от порядка, в котором смешиваются трейты. Для общих последовательностей мы можем использовать порядок «сзади наперед», чтобы вывестиsuperпоследовательность звонков.

Возьмите код выше.

new BasicIntQueue with Incrementing with Filtering

Порядок выполнения супер кода в порядке от задней части к передней.

Filtering -> Incrementing -> BasicIntQueue 

приведите конкретный пример

Например, на этот раз я выполняюput(1)кода, то в соответствии с приведенным выше порядком выполнения,

выполнить первымFilteringизputМетод определяет, больше ли значение 1, находит его допустимым и передает значение 1 вIncrementingсерединаputметод,IncrementingсерединаputМетод добавляет 1 к значению и передает его вBasicIntQueueЗатем поместите конечное значение 2 в очередь.

Процесс выполнения приведенного выше кодаstackable modificationsОсновной. Итак, здесь вы можете понять приведенные выше разные результаты из-за разных порядков смешивания.

Кроме того, говоря о динамике, мы также можем кратко рассказать о ней здесь. В Яве,superстатический иtraitсерединаsuperдинамичность в резком контрасте. А преимущества и мощь, приносимые динамикой, мы также видели парочку в содержании этого раздела. На самом деле динамическое извлечение — это дизайнерская идея, и она уже давно существует вокруг нас. Например, мы знакомы с внедрением зависимостей IOC, аспектно-ориентированным программированием АОП и технологией внешнего динамического сжатия.

Черты Исследуйте

Порядок построения признаков

trait Test {
    val name:String = "hello" //特质构造器的一部分
    println(name);  // 特质构造器的一部分
}

Как видно из приведенного выше кода, операторы выполнения, заключенные в фигурные скобки типажа, являются частью конструктора типажа.

Порядок конструкторов трейтов следующий: (ссылка из «Быстрое изучение Scala»)

  1. Конструктор суперкласса выполняется первым (то есть за ним следуетextendsпосле урока)
  2. Инициализаторы признаков выполняются после инициализаторов суперкласса и перед инициализаторами класса.
  3. Признаки строятся слева направо
  4. Родительский признак строится первым
  5. конструктор класса

Например

class SavingAccount extends Account with FileLogger with ShortLogger

trait ShortLogger extends Logger

trait FileLogger extends Logger

Вышеупомянутые конструкторы будут выполняться в следующем порядке

  1. Account(суперкласс)
  2. Logger(родительский признак первого признака)
  3. FileLogger(первая черта)
  4. ShortLogger(Второй признак слева направо, его родительский признакLoggerбыл построен и больше не будет строиться)
  5. SavingAccount(конструктор класса)

Линеаризация

На самом деле за последовательной реализацией вышеперечисленных конструкторов стоит техника, называемая «линеаризацией».

Возьмите приведенный выше код в качестве примера

class SavingAccount extends Account with FileLogger with ShortLogger

Приведенный выше код будет линеаризован и проанализирован как

>>означает, что правая часть будет построена первой

lin(SavingsAccount) = SavingsAccount >> lin(ShortLogger) >> lin(FileLogger) >> lin(Account)

= SavingsAccount >> (ShortLogger >> Logger) >> (FileLogger >> Logger) >> Account

= SavingsAccount >> ShortLogger >> FileLogger >> Logger >> Account

Внимательно наблюдая за результатами следующей линеаризации, вы обнаружите, что указанный выше порядок является порядком, в котором выполняются конструкторы. В то же время линеаризация также даетsuperПорядок выполнения, например, вShortLoggerвызыватьsuperпозвонит правильноFileLoggerметод в , в то время какFileLoggerсерединаsuperпозвонит правильноLoggerметоды в , и так далее.

Инициализация поля признака

Поэтому, поскольку время выполнения конструктора типажа предшествует выполнению конструктора класса, особое внимание следует уделять времени выполнения полей при инициализации полей в свойстве, чтобы избежать ситуации с нулевыми указателями. Например, следующий код выдаст ошибку

trait Hello {
  val name:String
  val out = new PrintStream(name)
}

val test = new Test with Hello {
    val name = "Rhyme" // Error 类构造器晚于特质构造器
}

Решения提前定义или懒值

Использование предопределенного кода показано ниже

val test = new { 
    val name = "Rhyme" //先于所有的构造器执行
}Test with Hello 

Способ определения заранее делает код менее элегантным, мы также можем использовать метод ленивых значений

Способ использования ленивого значения следующий:

trait Hello {
  val name:String
  lazy val out = new PrintStream(name) // 使用懒值,延迟name的初始化
}

Ленивое значение возвращается, чтобы проверить, было ли поле инициализировано перед каждым использованием, и есть определенные накладные расходы на использование. Тщательное рассмотрение перед использованием

Из-за нехватки места околоtrait, мы остановимся здесь. Я надеюсь, что эта статья поможет вам узнать и понятьtraitПредложите небольшую помощь. В следующей главе мы представимtraitЧуть более продвинутое использование, типы self и типы структур.