Scala #
Переменные #
val x // константа
def x // вычисляются каждый раз, когда на них ссылаются
lazy val x // вычисляется не больше одного раза в момент первого к ней обращения
var x // "настоящая" переменная, которая может быть переопределена
Области видимости #
{
val x = "Inner"
println(x)
}
println(x) // Error!!1!
Пространства имен #
Идентификаторы: #
- стабильные
- package
- параметр функции
- val
- lazy val
- object
- нестабильные:
- def
Импортирование: #
import com.example.Module.{name1, name2} // нескольких сущностей из пакета
import com.example.Module._ // все имена из пространства
import com.example.Module.{name1 => newName} // переименование при импорте
import com.example.Module.{_, name1 => _} // импортирование всех имен, кроме указанных
Типы #
A <: B // Тип А является подтипом типа В, когда все значения А могут использоваться как значения типа В.
A <: Any // Надтип для любого другого типа
A <: AnyRef // Надтип для любых ссылочных типов
A <: AnyVal // Надтип для любых примитивных типов
val x: String = "Some string"
val y: AnyRef = x
val z: Any = y
val a: Int = 4
val b: AnyVal = a
val c: Any = b
Nothing <: // является подтипом любого типа, в основном используется при генерации ошибок
Примитивные типы: #
- Целые числа (знаковые)
- Byte (1 byte)
- Short (2 byte)
- Int (4 byte)
- Long (8 byte)
- Дробные числа:
- Float (4 byte)
- Double (8 byte)
- Символы
- Char (2 byte)
- Булевы значения:
- Boolean
- Единичный тип:
- Unit (значение этого типа константно, используется, например, для типа значения, возвращаемого из функции, если оно нас не интересует)
Числа #
val a = 3 // Int
val b = -5L // Long
val c = 10.0 // Double
val d: Float // Явно указанный Float
val e = 1.03e1 // Вариант записи с мантиссой (научная форма записи)
Алгебраические операции с числами: #
val x = -5
val y = 3
x + y // Сложение
x - y // Вычитание
x * y // Умножение
x / y // Целочисленное деление
x % y // Взятие остатка
-x // Унарный минус
Побитовые операции с числами: #
val x = 0xF
val y = 0XA1
x >> y // побитовый сдвиг вправо
x << y // -//- влево
x | y // -//- ИЛИ
x & y // -//- И
x ^ y // -//- исключающее ИЛИ
~x // -//- инверсия
Порядок операций (по возрастанию, определяется по первому символу оператора) #
- символы
- |
- ^
- &
- = !
- <>
- :
- + -
- * / %
- все остальные знаки
Ссылочные типы для больших чисел #
val x = BigInt(10) // Целочисленный тип, содержащий неограниченное количество цифр
val y = BigDecimal(10) // Дробное число, так же не ограниченное по длине
val z = BigInt("1000") // Эти типы могу быть инициализированы как числом, так и строчным представлением числа
x pow 100 // Операция возведения в степень для BigInt, не дающая переполнения
x gcd y // Нахождение наибольшего общего делителя для двух больших чисел
Булевые значения #
val a = true
val b = false
Операции сравнения: #
val a = 1 > 5 // Больше
val b = 1 < 5 // Меньше
val c = 1 <= 5 // Меньше или равно
val d = 1 >= 5 // Больше или равно
val e = 1 == 5 // Равно
val f = 1 != 5 // Не равно
Логические операции: #
val x = 1 > 5
val y = "Example" contains "a"
x && y // И
x || y // ИЛИ
!x // НЕ
Равенство: #
Сравнивать объекты разных типов можно, но компилятор выдаст предупреждение.
Сравнение по ссылке:
val x = "Te"
val y = "st"
val a = x + y
val b = x + y
a == b // true, так как значения одинаковые
a eq b // false, так как ссылки разные
В Java все константные строки интернированы: компилятор производит оптимизацию, в результате чего, одинаковые строковые литералы ссылаются на один и тот же объект в памяти. Поэтому:
val s1 = "foo"
val s2 = "foo"
println(s1 eq s2) // true
Строки #
val name = "Username" // Объявление строки
val greet = "Hello" + name + "!!!" // Конкатенация
val greet2 = s"Hello $name!!!" // Интерполяция
val multiLine =
"""
multi
line
string
"""
Операции: #
val s = "aaabbb"
s.startsWith("aa")
s.endsWith("bb")
s.contains("ab")
s.matches("a+b+") // regexp
Практически любой объект имеет метод toString:
val x = 2
x.toString
"x is " + x // При конкатенации приведение к строке происходит автоматически
Методы #
Способ оформления переиспользуемого фрагмента кода.
def plusOne(number: Int): Int = number + 1
| | | | | |
| | | | | +-- тело метода
| | | | +-- возвращаемый тип
| | | +-- тип параметра
| | +-- имя параметра
| +-- имя метода
+-- ключевое слово
// Вызов метода
plusOne(4) // 5
Тип результата может быть опущен в случае, если он очевидно следует из тела метода. В этом случае тип будет выведен автоматически:
def plusOne(number: Int) = number + 1
Несколько параметров передаются через запятую:
def plusOne(x: Int, y: Int, z: Int): Int = x + y + z
plusOne(1, 2, 3) // 6
У метода может быть несколько списков параметров. В этом случае при вызове метода входные параметры так же должны быть сгруппированы соответствующим образом:
def plusOne(x: Int, y: Int)(z: Int): Int = x + y + z
plusOne(1, 2)(3) // 6
Так же у метода может не быть параметров, в этом случае, он воспринимается как “переменная”, значение которой вычисляется каждый раз при обращении (см. начало)
def sixty: Int = 10 * 6
sixty // 60
Тело метода может быть обернуто в фигурные скобки. Возвращено будет значение последнего выражения.
def plusAndPrint(x: Int, y: Int): Int = {
val result = x + y
println(s"$x + $y = $result")
result
}
plusAndPrint(2, 4) // prints "2 + 4 = 6", returns 6
Если метод не производит какие-либо действия, не возвращая ничего, то его возвращаемым типом будет Unit.
def plusAndPrint(x: Int, y: Int): Unit = {
val result = x + y
println(s"$x + $y = $result")
}
plusAndPrint(2, 4) // prints "2 + 4 = 6"
Методы могут быть вызваны в телах других методов. В этом случае вложенный метод может ссылаться на параметры внешнего:
def plusMul(q: Int, x: Int, y: int): Int = {
def mul(u: Int) = q * u
mul(x) + mul(y)
}
plusMul(10, 2, 3) // 50
Последний параметр может быть объявлен повторяемым, то есть он может использоваться как коллекция:
def sumAllTimes(u: Int, nums: Int*): Int = u * nums.sum
sumAllTimes(3, 1, 2, 3) // 3 * (1 + 2 + 3) = 18
Значение по умолчанию и именованные аргументы:
def plus3(x: Int, y: Int = 0, z: Int = 0): Int = 100 * x + 10 * y + z
plus3(1) // 100
plus3(1, 2) // 120
plus3(1, 2, 3) // 123
plus3(x = 1) // 100
plus3(1, z = 2) // 102
plus3(x = 1, z = 3, y =2) // 123
При передаче параметра по имени его значение вычисляется не всегда а только тогда, когда в этом действительно есть необходимость. Но стоит отметить, что он будет вычислен столько раз, сколько раз на него будут ссылаться, что может привести к сложно отлаживаемым багам:
def replaceNegative(x: Int, z: => Int): Int = if (x >= 0) x else z
replaceNegative(1, 3 * 3 * 3) // 1
replaceNegative(-1, 3 * 3 * 3) // 27
Для рекурсивных функций указание типа обязательно
def sumRange(from: Int, to: Int): Int = {
if ( to < from ) 0
else from + sumRange(from + 1, to)
}
sumRange(1, 10) // 55
Функции #
Функция - выражение, которое может быть использовано как метод.
val x: Int => Int = ...
val y: (Int, Int) => Int = ... // Максимальное количество параметров - 22
Лямбда-абстракция #
Значения функций можно задавать с помощью лямбда-синтаксиса
val x: Int => Int = x => x + 1
val y: (Int, Int) => Int = (x, y) => x + y
Тип параметров можно указывать прямо в лямбда-выражении, в этом случае компилятор попробует вывести тип самостоятельно
val addOne = (x: Int) => x + 1
val plus = (x: Int, y: Int) => x + y
Короткая запись может быть использована в случае, если каждый параметр используется только один раз и вызывается и все параметры вызываются в том же порядке, в котором передаются в функцию:
val addOne: Int => Int = _ + 1
val plus = (_: Int) + (_: Int)
Эта-конверсия #
Еще один метод объявления функции, путем конвертации метода:
def addOnde(x: Int) = x + 1
val add1 = addOne _
def plus(x: Int, y: Int) = x + y
val pl: (Int, Int) => Int = plus // подчеркивание можно опустить, если для компилятора очевидно, что в этом месте ожидается функция