Основы
Переменные и типы
Предпочитайте val (неизменяемое) var (изменяемое) для более безопасного, предсказуемого кода. Scala выводит типы, но явные аннотации помогают читаемости для публичных API. Всё — объект, нет примитивов (Int, Boolean — классы).
val name = "Alice" // immutable (preferred)
var age = 30 // mutable
val pi: Double = 3.14159
val isDev: Boolean = true
val nums: List[Int] = List(1, 2, 3)
println(name.getClass) // class java.lang.StringИнтерполяция строк
s"..." включает интерполяцию ${expr}. f"..." добавляет printf-подобное форматирование (%s, %.2f). raw"..." отключает escape-последовательности. Эти префиксы делают построение строк типобезопасным и читаемым.
val name = "Alice"
val age = 30
println(s"Name: ${name}, Age: ${age}")
println(s"Next year: ${age + 1}")
println(s"Upper: ${name.toUpperCase}")
println(f"${name}%s weighs ${65.5}%.1f kg") // formatted
println(raw"No \n escape") // raw stringКортежи
Кортежи группируют 2-22 гетерогенных значения. Доступ через _1, _2 (1-индексация). Деструктуризация через val (a, b) = tuple. Для более 2 элементов предпочитайте case-классы для именованных полей и лучшей читаемости.
val pair = ("Alice", 30)
println(pair._1) // Alice
println(pair._2) // 30
val (name, age) = pair // destructure
println(s"${name}: ${age}")
// Scala 3: val p = ("a", 1, 2.0)
val triple = ("a", 1, 2.0)
println(triple._3) // 2.0Вывод типа и приписывание
Scala выводит типы для локальных переменных и возвращаемых типов. Используйте явные типы для публичных API, рекурсивных функций и неоднозначных случаев. Приписывание типа (expr: Type) принуждает тип — полезно для upcast или устранения неоднозначности.
val x = 42 // Int inferred
val y: Long = 42 // explicit Long
val z = 42: Long // type ascription
val list = List(1, 2, 3) // List[Int]
val mixed: List[Any] = List(1, "a", true)
def double(x: Int) = x * 2 // return type inferredUnit и Nothing
Unit подобен void (одно значение: ()). Nothing — нижний тип без экземпляров, используется для функций, которые никогда не возвращаются (throw, бесконечный цикл). Nothing — подтип всех типов, обеспечивая гибкий вывод типа.
def printIt(x: Int): Unit = println(x) // like void
val u: Unit = () // Unit has one value: ()
// Nothing is the bottom type—no instances
def error(msg: String): Nothing =
throw new RuntimeException(msg)
// Nothing is a subtype of everything
val n: Nothing = error("boom")Строки
Распрост ранённые строковые методы
Строки Scala — Java-строки с дополнительными методами через неявные преобразования (StringOps). Большинство методов возвращают новые строки (неизменяемые). Используйте их вместо ручных циклов для ясности и корректности.
val s = "Hello, World"
println(s.length) // 12
println(s.toUpperCase) // HELLO, WORLD
println(s.toLowerCase) // hello, world
println(s.split(", ")) // Array(Hello, World)
println(s.replace("o", "0")) // Hell0, W0rld
println(s.reverse) // dlroW ,olleH
println(s.contains("World")) // trueМногострочные строки (тройные кавычки)
Строки в тройных кавычках сохраняют все пробелы и переносы. Используйте stripMargin с | для чистого выравнивания кода — сохраняется только текст после |. Идеально для SQL, JSON или шаблонов, встроенных в код.
val sql = """
SELECT * FROM users
WHERE age > 18
ORDER BY name
"""
println(sql.trim)
// StripMargin for clean indentation
val text = """|Hello
|World""".stripMargin
println(text) // Hello
WorldПостроение строк
mkString объединяет коллекции с опциональным префиксом/суффиксом — идиоматично и эффективно. Используйте StringBuilder для построения больших строк в циклах. Избегайте повторной конкатенации + в циклах (создаёт много промежуточных объектов).
val parts = List("apple", "banana", "cherry")
println(parts.mkString(", ")) // apple, banana, cherry
println(parts.mkString("[", ", ", "]")) // [apple, banana, cherry]
val sb = new StringBuilder
for (p <- parts) sb.append(p).append(" ")
println(sb.toString.trim)Строка в число
toInt/toDouble вызывают исключение при некорректном вводе. Используйте toIntOption (Scala 2.13+) для безопасного разбора, возвращающего Option. Для массового разбора используйте Try или Either для функциональной обработки ошибок без исключений.
val n = "42".toInt // 42
val d = "3.14".toDouble // 3.14
val b = "true".toBoolean // true
val safe = "abc".toIntOption // Some(42) or None
// Handling errors
val result = try "x".toInt catch { case _ => 0 }
println(result) // 0Регулярные выражения
.r преобразует строку в Regex. findFirstIn возвращает Option, findAllIn возвращает итератор. Используйте в сопоставлении шаблонов с case email(e) => для извлечения. Regex — это Pattern Java под капотом.
import scala.util.matching.Regex
val email: Regex = "[\w.]+@[\w]+\.[a-z]+".r
val text = "Contact: [email protected]"
email.findFirstIn(text) match {
case Some(e) => println(s"Found: ${e}")
case None => println("No email")
}
val replaced = "[0-9]+".r.replaceAllIn("a1b2c3", "#")
println(replaced) // a#b#c#Структуры данных
List и Seq
List — неизменяемый односвязный список: O(1) head/prepend, O(n) случайный доступ. Используйте Vector для случайного доступа (O(1) эффективный). +: добавляет в начало, :+ добавляет в конец. Предпочитайте неизменяемые коллекции для потокобезопасности.
val nums = List(1, 2, 3, 4, 5)
println(nums.head) // 1
println(nums.tail) // List(2,3,4,5)
println(nums.reverse) // List(5,4,3,2,1)
println(nums.take(2)) // List(1, 2)
println(nums.drop(2)) // List(3, 4, 5)
println(nums.mkString) // 12345
val combined = 0 +: nums :+ 6 // List(0,1,2,3,4,5,6)Map
Map неизменяем — операции возвращают новые Map. Используйте get(key) для Option-доступа, getOrElse для значений по умолчанию. + добавляет/обновляет, - удаляет. Для изменяемых map используйте scala.collection.mutable.Map. Ключи должны быть Hashable.
val ages = Map("Alice" -> 30, "Bob" -> 25)
println(ages("Alice")) // 30 (throws if missing)
println(ages.getOrElse("Eve", 0)) // 0 (safe)
val updated = ages + ("Eve" -> 28) // new Map
val removed = ages - "Bob"
ages.foreach { case (k, v) => println(s"${k}: ${v}") }
println(ages.keys) // Set(Alice, Bob)Set
Set неизменяем с O(1) contains. union (|), intersect (&), diff (~) для операций над множествами. + добавляет, - удаляет. Используйте для дедупликации и проверки принадлежности. Изменяемый вариант: scala.collection.mutable.Set.
val a = Set(1, 2, 3)
val b = Set(3, 4, 5)
println(a union b) // Set(1,2,3,4,5)
println(a intersect b) // Set(3)
println(a diff b) // Set(1,2)
println(a subsetOf(Set(1,2,3,4))) // true
val added = a + 6 // Set(1,2,3,6)Option (null-безопасность)
Option заменяет null — Some(value) или None. Используйте map/filter/flatMap для преобразований, getOrElse для значений по умолчанию. For-comprehensions работают с Option. Это устраняет NullPointerException, делая отсутствие явным в типе.
def findUser(id: Int): Option[String] =
if (id == 1) Some("Alice") else None
val name = findUser(1)
println(name.getOrElse("Unknown")) // Alice
println(name.map(_.toUpperCase)) // Some(ALICE)
println(name.filter(_.startsWith("A"))) // Some(Alice)
val result = for {
n <- findUser(1)
if n.startsWith("A")
} yield n.toUpperCase // Some(ALICE)Array и Vector
Array — изменяемый Java-массив (самый быстрый, но без функциональных обновлений). Vector неизменяем с эффективным O(1) случайным доступом и обновлениями — предпочтителен для неизменяемых коллекций со случайным доступом. Используйте List для последовательного, Vector для индексного доступа.
val arr = Array(1, 2, 3, 4) // mutable, Java array
arr(0) = 10
println(arr(0)) // 10
val vec = Vector(1, 2, 3, 4) // immutable, fast random access
println(vec(2)) // 3
val updated = vec.updated(0, 10) // Vector(10,2,3,4)
// Vector: O(1) random access + immutable
// Array: mutable, Java interop, fastestПоток управления
If / Else (выражение)
В Scala if/else — выражение, возвращающее значение. Это устраняет необходимость тернарного оператора. Обе ветви должны иметь совместимые типы. Используйте для лаконичного условного присваивания.
val score = 85
val grade =
if (score >= 90) "A"
else if (score >= 80) "B"
else if (score >= 70) "C"
else "F"
println(grade) // B
// if returns a value—no ternary operator neededFor-comprehension
for-comprehensions итерируют и могут фильтровать (if guards) и преобразовывать (yield). Без yield это цикл; с yield строит коллекцию. to включает конец, until исключает. Эквивалентно цепочкам flatMap/map/filter.
for (i <- 1 to 5) println(i) // 1 2 3 4 5
for (i <- 1 until 5) print(i) // 1 2 3 4
// With yield (generates collection)
val doubled = for (n <- List(1,2,3)) yield n * 2
println(doubled) // List(2, 4, 6)
// With filter (guard)
val evens = for (n <- 1 to 10 if n % 2 == 0) yield n
println(evens.toList) // List(2,4,6,8,10)Match (сопоставление шаблонов)
match — мощное сопоставление шаблонов Scala, как switch на стероидах. Поддерживает литералы, OR (|), guards (if), сопоставление типов и деструктуризацию. Должно быть исчерпывающим (компилятор предупреждает о пропущенных случаях). Это выражение, возвращающее значение.
val n = 2
val label = n match {
case 0 => "zero"
case 1 | 2 | 3 => "small"
case x if x < 10 => "medium"
case _ => "large"
}
println(label) // small
// Match on types
def describe(x: Any): String = x match {
case i: Int => s"Int: ${i}"
case s: String => s"String: ${s}"
case _ => "unknown"
}While и Do-While
while/do-while — императивные циклы, возвращающие Unit. Требуют изменяемого состояния (var). Предпочитайте for-comprehensions или ре курсию для идиоматичного функционального Scala. Используйте while только когда требует производительность или для побочных эффектов.
var i = 0
while (i < 3) {
println(i)
i += 1
}
var j = 0
do {
println(j)
j += 1
} while (j < 3)
// Prefer recursion or for-comprehensions for immutabilityTry / Catch / Finally
try/catch/finally как в Java, но использует сопоставление шаблонов в catch. Предпочитайте Try для функциональной обработки ошибок — оборачивает исключения как значения Success/Failure, обеспечивая цепочки map/flatMap без шаблонности try/catch.
import scala.util.{Try, Success, Failure}
val result = try {
"abc".toInt
} catch {
case e: NumberFormatException => 0
} finally {
println("cleanup")
}
println(result) // 0
// Functional alternative
val r2 = Try("abc".toInt).getOrElse(0)
println(r2) // 0Функции
Определение метода
Методы используют def name(params): ReturnType = body. Поддерживаются параметры по умолчанию и именованные аргументы. Возврат Unit = только побочный эффект. Одно-выражительные тела опускают фигурные скобки. = обязателен (без него это процедура, возвращающая Unit).
def add(a: Int, b: Int): Int = a + b
def greet(name: String, greeting: String = "Hello"): String =
s"${greeting}, ${name}!"
def log(msg: String): Unit = println(msg)
println(add(3, 4)) // 7
println(greet("Alice")) // Hello, Alice!
println(greet("Bob", greeting = "Hi")) // named argЛямбда (анонимная функция)
Лямбды: (params) => body. Используйте _ как сокращение для одного параметра (x => x * 2 становится _ * 2). Несколько _ ссылаются на разные параметры (_ + _). Лямбды — первого класса, передавайте и х в map, filter, reduce и т.д.
val square = (x: Int) => x * x
println(square(5)) // 25
val nums = List(1, 2, 3)
println(nums.map(_ * 2)) // List(2, 4, 6)
println(nums.filter(_ > 1)) // List(2, 3)
println(nums.reduce(_ + _)) // 6
// _ is shorthand for the parameterФункции высшего порядка
Функции высшего порядка принимают или возвращают функции. Это обеспечивает мощные абстракции: map/filter/reduce, композиция, частичное применение. makeAdder возвращает замыкание, захватывающее n. Это сердце функционального программирования.
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
println(applyTwice(_ + 3, 5)) // 11
def makeAdder(n: Int): Int => Int = _ + n
val add5 = makeAdder(5)
println(add5(10)) // 15
// Functions returning functions = currying-likeКаррирование и частичное применение
Каррирование разделяет параметры на несколько списков: def f(a)(b). Частичное применение через _ создаёт специализированные функции. Несколько списков параметров улучшают вывод типа (компилятор может вывести типы f из списка). Распространено в API коллекций.
def add(a: Int)(b: Int): Int = a + b // curried
val add5 = add(5)_ // partially applied
println(add5(3)) // 8
// Multiple parameter lists
def foldLeft[A, B](list: List[A])(z: B)(f: (B, A) => B): B = ???
// Helps type inference
val sum = List(1,2,3).foldLeft(0)(_ + _)By-Name и Lazy
By-name параметры (=> T) вычисляются лениво при каждом использовании — полезно для логирования (пропуск дорогого msg, если отключено) и пользовательских управляющих структур. lazy val откладывает инициализацию до первого доступа — для дорогих или опциональных значений.
// By-name parameter: evaluated on each use
def debug(msg: => String): Unit =
if (debugEnabled) println(msg)
// Lazy evaluation
lazy val expensive = computeHeavy()
println(expensive) // computed now
def computeHeavy(): Int = { println("computing"); 42 }Классы и ООП
Класс и конструктор
Параметры первичного конструктора в сигнатуре класса. Параметры val становятся неизменяемыми полями (публичный getter), var — изменяемыми. Тело класса ЕСТЬ конструктор. Вспомогательные конструкторы используют def this(...) и должны вызывать другой конструктор.
class Person(val name: String, val age: Int) {
// Constructor params with val/var become fields
def greet: String = s"Hi, I'm ${name}"
def isAdult: Boolean = age >= 18
}
val p = new Person("Alice", 30)
println(p.greet) // Hi, I'm Alice
println(p.name) // Alice (val field)
println(p.isAdult) // trueCase-класс
Case-классы — неизменяемые классы данных с автогенерацией equals, hashCode, toString, copy и companion-объектом с apply/unapply. Используйте для моделирования данных и сопоставления шаблонов. 'new' не нужен. Это основа ADT в Scala.