Эта серия статей предназначена для занятых программистов, которые хотят выучить Scala быстро, за 2 часа или меньше. Эти статьи представляют собой письменную версию мини-курса Rock the JVM Scala на Light Speed, которую вы можете найти бесплатно на YouTube или на веб-сайте Rock the JVM в виде видео.
Вам также может понравиться:
Scala на скорости света, часть 1: основы
Это вторая статья серии, в которой основное внимание будет уделено Scala как объектно-ориентированному языку. Вы можете посмотреть его в виде видео здесь или во встроенном видео ниже.
В предыдущей части мы узнали:
- Как настроить проект Scala в IntelliJ IDEA
- Значения, выражения и типы
- Функции и рекурсия
- Тип блока
Классы
Ничего особенного. У Scala такое же представление о классе, которое вы, вероятно, видели на других языках.
Scala
1
class Animal {
2
// define fields
3
val age: Int = 0
4
// define methods
5
def eat() = println("I'm eating")
6
}
7
val anAnimal = new Animal
9
anAnimal.eat()
Поля определяются с помощью val
методов def
, и они работают так, как мы обсуждали в части 1. Доступ к полям и методам осуществляется с помощью оператора точки, как и в других языках.
Scala имеет наследование одного класса (очень похоже на Java), как показано ниже:
Scala
xxxxxxxxxx
1
// inheritance
2
class Dog(name: String) extends Animal
3
val aDog = new Dog("Lassie")
Классы в Scala могут принимать аргументы - это аргументы конструктора. Когда вы определяете класс, вы также определяете сигнатуру его конструктора. В этом случае конструктор Dog принимает один аргумент String.
Помните, что аргумент конструктора не является полями - приведенный ниже код вызовет ошибку.
Scala
xxxxxxxxxx
1
aDog.name
Чтобы сохранить аргумент конструктора как поле, поместите val
перед именем аргумента:
Scala
xxxxxxxxxx
1
class Dog(val name: String) extends Animal
Обратите внимание, что ничто не является изменчивым (у нас есть понятие переменной в Scala, но это сильно не рекомендуется). В общем, мы работаем с неизменяемыми структурами данных: любая предполагаемая модификация существующего экземпляра должна возвращать новый (измененный) экземпляр.
Наследование, признаки и обозначения методов
Scala имеет тот же полиморфизм подтипа, который вы видели в других статически типизированных языках:
Scala
xxxxxxxxxx
1
val aDeclaredAnimal: Animal = new Dog("Hachi") // off-topic: Hachi is an amazing movie
2
aDeclaredAnimal.eat() // the most derived method will be called at runtime
Это значит, что мы объявили значение Animal, но во время выполнения оно действует как Dog. Производный класс может, конечно, переопределять их методы суперкласса.
В Scala также есть понятие абстрактных классов , где вы можете оставить поля / методы без реализации:
Scala
xxxxxxxxxx
1
// abstract class
2
abstract class WalkingAnimal {
3
val hasLegs = true // by default public, can restrict by adding protected or private
4
def walk(): Unit
5
}
Как и выше комментарий упоминает, все поля / методы по умолчанию являются открытыми , если мы не ограничивать их видимость с private
и protected
модификаторов. Они работают точно так же, как в Java: private
ограничивают доступ только к классу, protected
ограничивают доступ к классу и всем производным классам.
У нас также есть понятие «интерфейс», тип со всем, что является абстрактным - это trait
:
Scala
xxxxxxxxxx
1
// "interface" = ultimate abstract type
2
trait Carnivore {
3
def eat(animal: Animal): Unit
4
}
(Хотя мы также можем реализовать поля / методы в чертах; длинная история)
Scala имеет наследование одного класса и наследование нескольких признаков:
Scala
xxxxxxxxxx
1
trait Philosopher {
2
def ?!(thought: String): Unit // valid method name
3
}
4
// single-class inheritance, multi-trait "mixing"
6
class Crocodile extends Animal with Carnivore with Philosopher {
7
override def eat(animal: Animal): Unit = println("I am eating you, animal!")
8
override def ?!(thought: String): Unit = println(s"I was thinking: $thought")
9
}
Во-первых, обратите внимание, что крокодил происходит от животных и двух других черт.
Во-вторых, обратите внимание, что у Философа есть метод, который называется ?!
. Scala очень удобна для именования методов, где имена методов могут начинаться с буквенно-цифровых символов. Такие имена , как :::
, ?#
, -->
, ~>
все в силе, и они используются на практике. Примеры включают ::
в работе со списками, ?
и !
в работе с актерами Akka и ~>
в работе с Akka Streams, чтобы назвать несколько.
В-третьих, смотрите ниже.
Scala
xxxxxxxxxx
1
val aCroc = new Crocodile
2
aCroc.eat(aDog)
3
aCroc eat aDog // equivalent
4
aCroc ?! "What if we could fly?"
Выражения в строках 3 и 4 выше называются инфиксной нотацией, которая применяется к методам с одним аргументом. Вместо instance.method(argument)
структуры вызова мы можем использовать instance method argument
. Это делает Scala чрезвычайно выразительным, потому что мы можем сделать выражения похожими на естественный язык (крокодил ест собаку, строка 3) или как математические операции (строка 4).
Что подводит нас к другому моменту. Даже простые математические операторы, такие как 1 + 2, на самом деле являются вызовами методов: 1 + 2
идентичен 1.+(2)
: мы применяем метод плюс числа 1 к аргументу 2.
Наконец, при наследовании в Scala есть понятие анонимного класса , очень похожего на Java:
Scala
xxxxxxxxxx
1
// anonymous classes
2
val dinosaur = new Carnivore {
3
override def eat(animal: Animal): Unit = println("I can eat pretty much anything")
4
}
Что это, по сути, говорит компилятору (псевдо-Scala, но не слишком сильно):
Scala
xxxxxxxxxx
1
class Carnivore_Anonymous_35728 extends Carnivore {
2
override def eat(animal: Animal): Unit = println("I can eat pretty much anything")
3
}
4
val dinosaur = new Carnivore_Anonymous_35728
Объекты и Спутники
В Scala синглтоны являются первоклассными элементами с ключевым словом object
:
Scala
x
1
// singleton object
2
object MySingleton { // the only instance of the MySingleton type
3
val mySpecialValue = 53278
4
def mySpecialMethod(): Int = 5327
5
}
Определив типobject
, вы определили как его тип, так и отдельный экземпляр этого типа , и теперь вы можете использовать его как любое другое значение.
Вы также можете поместить синглтон - мы будем называть его объектами - и класс (или признак) с тем же именем в одном файле: они называются компаньонами . Они могут получить доступ к приватным полям друг друга.
Scala
xxxxxxxxxx
1
class Animal { ... }
2
object Animal { // companions - this is a companion object
4
// companions can access each other's private fields/methods
5
val canLiveIndefinitely = false
6
}
7
val animalsCanLiveForever = Animal.canLiveIndefinitely // "static" fields/methods
Обратите внимание, как мы получаем доступ к полю или методу, не ссылаясь на экземпляр Animal, но на одноэлементный Animal (что всегда разные вещи). Это аналогично «статическим» полям / методам, которые вы видите на других языках.
Поскольку весь код, который мы пишем, является частью экземпляра класса или синглтона, многие люди считают Scala более объектно-ориентированным, чем Java!
Применить метод
Методы в Scala названы apply
особенными.
Scala
xxxxxxxxxx
1
object MySingleton {
2
def apply(x: Int): Int = x + 1
3
}
Методы применения могут быть вызваны двумя способами для экземпляра синглтона или класса:
Scala
xxxxxxxxxx
1
MySingleton.apply(65)
2
MySingleton(65) // equivalent to MySingleton.apply(65)
Одним из них является регулярный вызов метода; другой новый. Apply позволяет экземплярам «вызываться» как функции. Это окажется важным в следующей части, когда мы поговорим о функциональном программировании. Вы можете добавить методы применения к любому классу с любым видом аргументов. Другой пример:
Scala
xxxxxxxxxx
1
class MyArrayList {
2
def apply(index: Int): Int = ... // assume it gets the element at the given index
3
}
4
val myArrayList = new MyArrayList
6
val thirdElement = myArrayList(2) // same as myArrayList.apply(2)
Библиотека коллекций Scala использует эту технику для многих коллекций.
Одна хитрость: мы можем использовать apply в сопутствующих объектах как фабричные методы:
Scala
xxxxxxxxxx
1
class Dog(name: String)
2
object Dog {
3
def apply(name: String) = new Dog(name)
4
}
5
val myDog = Dog("Laika") // same as Dog.apply("Laika")
Таким образом, мы можем создавать экземпляры классов без new
. В следующей версии Scala 3 это будет сделано по умолчанию компилятором.
Кейсы
На практике нам часто приходится работать с облегченными структурами данных . Нам нужны они:
- Иметь разумные равные / хэш-коды, чтобы их можно было легко включать в коллекции
- Быть сериализуемым, когда мы отправляем их по проводам
- Быть простым в сборке
- Чтобы было легко разобрать * (мы покажем, как позже, когда мы поговорим о сопоставлении с образцом)
Чтобы нам не приходилось каждый раз писать подобный код, в Scala есть понятие класса case для вышеуказанных потребностей.
Scala
xxxxxxxxxx
1
case class Person(name: String, age: Int)
2
val bob = Person("Bob", 54)
Когда вы определяете класс case, компилятор автоматически генерирует:
- Глубокие равно и hashCode
- Формат сериализации
- Сопутствующий объект с применением метода фабрики, как показано 30 секунд назад
- Разумный формат деконструкции * (поговорим позже при сопоставлении с образцом)
- И еще: методы копирования, итераторы продукта, функции конструктора карри (пока не нужно заботиться об этом)
Исключения
Основываясь на JVM, Scala имеет то же понятие исключений, что и Java. Приведенный ниже код должен служить примером. Это похоже на другие языки, которые поддерживают исключения, например, Java, C #, C ++ (не заставляйте меня начинать с исключениями в C ++).
Scala
xxxxxxxxxx
1
try {
2
// code that can throw
3
val x: String = null
4
x.length // BOOM!
5
} catch { // in Java: catch(Exception e) {...}
6
case e: Exception => "some faulty error message"
7
} finally {
8
// execute some code no matter what
9
}
Для разработчиков Java: Scala не имеет понятия проверенного исключения, то есть типа, который вы должны объявить или который вы должны поймать.
Немного обобщения
В Scala мы можем присоединять аргументы типа к нашим классам / признакам, чтобы повторно использовать код, который мы пишем для нескольких типов. Вы наверняка видели это в других статически типизированных языках.
Scala
xxxxxxxxxx
1
abstract class MyList[T] {
2
def head: T
3
def tail: MyList[T]
4
}
В этом случае мы используем универсальный тип T, чтобы сделать механизм списка применимым к целым числам, строкам, лицам или другим объектам. Коллекции стандартных библиотек Scala являются общими. Примеры:
Scala
xxxxxxxxxx
1
val aList: List[Int] = List(1,2,3) // List.apply(1,2,3)
2
val first = aList.head // int
3
val rest = aList.tail
4
val aStringList = List("hello", "Scala")
5
val firstString = aStringList.head // string
Компилятор все еще выполняет вывод типов и проверку типов, поэтому мы всегда работаем с правильными типами.
Будьте на связи
В следующей статье мы поговорим о Scala как о функциональном языке программирования.