Файл: Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.12.2023
Просмотров: 737
Скачиваний: 11
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
72 Глава 3 • Дальнейшие шаги в Scala
Следовательно, код greetStrings(i)
преобразуется в код greetStrings.
apply(i)
. Получается, что элемент массива в Scala является просто вызовом обычного метода, ничем не отличающегося от любого своего собрата. Этот принцип не ограничивается массивами: любое использование объекта в от
ношении какихлибо аргументов в круглых скобках будет преобразовано в вызов метода apply
. Разумеется, данный код будет скомпилирован, только если в этом типе объекта определен метод apply
. То есть это не особый случай, а общее правило.
Рис. 3.1. Все операции в Scala являются вызовами методов
По аналогии с этим, когда присваивание выполняется в отношении пере
менной, к которой применены круглые скобки с одним или несколькими аргументами внутри, компилятор выполнит преобразование в вызов метода update
, получающего не только аргументы в круглых скобках, но и объект, расположенный справа от знака равенства. Например, код greetStrings(0) = "Hello"
будет преобразован в код greetStrings.update(0, "Hello")
Таким образом, следующий код семантически эквивалентен коду листин
га 3.1:
val greetStrings = new Array[String](3)
greetStrings.update(0, "Hello")
greetStrings.update(1, ", ")
greetStrings.update(2, "world!\n")
for i <- 0.to(2) do print(greetStrings.apply(i))
Шаг 8 . Используем списки 73
Концептуальная простота в Scala достигается за счет того, что все — от массивов до выражений — рассматривается как объекты с методами. Вам не нужно запоминать особые случаи, например такие, как существующее в Java различие между примитивными типами и соответствующими им типамиоболочками или между массивами и обычными объектами. Бо
лее того, подобное единообразие не вызывает больших потерь произво
дительности. Компилятор Scala везде, где только возможно, использует в скомпилированном коде массивы Java, элементарные типы и чистые арифметические операции.
Рассмотренные до сих пор в этом шаге примеры компилируются и выпол
няются весьма неплохо, однако в Scala имеется более лаконичный способ создания и инициализации массивов, который, как правило, вы и будете ис
пользовать (см. листинг 3.2). Данный код создает новый массив длиной три элемента, инициализируемый переданными строками "zero"
,
"one"
и "two"
Компилятор выводит тип массива как
Array[String]
, поскольку ему пере
даются строки.
Листинг 3.2. Создание и инициализация массива val numNames = Array("zero", "one", "two")
Фактически в листинге 3.2 вызывается фабричный метод по имени apply
, создающий и возвращающий новый массив. Метод apply получает пере
менное количество аргументов
1
и определяется в объекте-компаньоне
Array
Подробнее объектыкомпаньоны будут рассматриваться в разделе 4.3. Если вам приходилось программировать на Java, то можете воспринимать это как вызов статического метода по имени apply в отношении класса
Array
. Менее лаконичный способ вызова того же метода apply выглядит следующим об
разом:
val numNames2 = Array.apply("zero", "one", "two")
Шаг 8 . Используем списки
Одна из превосходных отличительных черт функционального стиля про
граммирования — полное отсутствие у методов побочных эффектов. Един
ственным действием метода должно быть вычисление и возвращение зна
чения. Получаемые в результате применения такого подхода преимущества
1
Списки аргументов переменной длины или повторяемые параметры рассматрива
ются в разделе 8.8.
74 Глава 3 • Дальнейшие шаги в Scala заключаются в том, что методы становятся менее запутанными, и это упро
щает их чтение и повторное использование. Есть и еще одно преимущество
(в статически типизированных языках): все попадающее в метод и выходя
щее за его пределы проходит проверку на принадлежность к определенному типу, поэтому логические ошибки, скорее всего, проявятся сами по себе в виде ошибок типов. Применять данную функциональную философию к миру объектов означает превратить эти объекты в неизменяемые.
Как вы уже видели, массив Scala — неизменяемая последовательность объ
ектов с общим типом. Тип
Array[String]
, к примеру, содержит только строки.
Изменить длину массива после создания его экземпляра невозможно, но вы можете изменять значения его элементов. Таким образом, массивы относятся к изменяемым объектам.
Для неизменяемой последовательности объектов с общим типом можно воспользоваться списком, определяемым Scalaклассом
List
. Как и в случае применения массивов, в типе
List[String]
содержатся только строки. Спи
сок Scala
List отличается от Javaтипа java.util.List тем, что списки Scala всегда неизменямые, а списки Java могут изменяться. В более общем смысле список Scala разработан с прицелом на использование функционального стиля программирования. Список создается очень просто, и листинг 3.3 как раз показывает это.
Листинг 3.3. Создание и инициализация списка val oneTwoThree = List(1, 2, 3)
Код в листинге 3.3 создает новую val
переменную по имени oneTwoThree
, инициализируемую новым списком
List[Int]
с целочисленными элемента
ми
1
,
2
и
3 1
. Изза своей неизменяемости списки ведут себя подобно строкам в Java: при вызове метода в отношении списка изза имени данного метода может создаваться впечатление, что обрабатываемый список будет изменен, но вместо этого создается и возвращается новый список с новым значением.
Например, в
List для объединения списков имеется метод, обозначаемый как
:::
. Используется он следующим образом:
val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour
1
Использовать запись new List не нужно, поскольку
List.apply()
определен в объ
ектекомпаньоне scala.List как фабричный метод. Более подробно объектыком
паньоны рассматриваются в разделе 4.3.
Шаг 8 . Используем списки 75
После выполнения этого кода oneTwoThreeFour будет ссылаться на
List(1,
2,
3,
4)
, но oneTwo попрежнему будет ссылаться на
List(1,
2)
, а threeFour
— на
List(3,
4)
. Ни один из списков операндов не изменяется оператором кон
катенации
:::
, который возвращает новый список со значением
List(1,
2,
3,
4)
. Возможно, работая со списками, вы чаще всего будете пользоваться оператором
::
, который называется cons cons добавляет новый элемент в на
чало существующего списка и возвращает полученный список. Например, если вы запустите этот код: val twoThree = List(2, 3)
val oneTwoThree = 1 :: twoThree значение oneTwoThree будет
List(1,
2,
3)
ПРИМЕЧАНИЕ
В выражении 1 :: twoThree метод :: относится к правому операнду — списку twoThree . Можно заподозрить, будто с ассоциативностью метода :: что-то не то, но есть простое мнемоническое правило: если метод используется в виде оператора, например a * b, то вызывается в отношении левого операнда, как в выражении a .*(b), если только имя метода не закан- чивается двоеточием . А если оно заканчивается двоеточием, то метод вызывается в отношении правого операнда . Поэтому в выражении 1 :: twoThree метод :: вызывается в отношении twoThree с передачей ему 1, то есть twoThree .::(1) . Ассоциативность операторов более подробно будет рассматриваться в разделе 5 .9 .
Исходя из того, что короче всего указать пустой список с помощью
Nil
, один из способов инициализировать новые списки — связать элементы с помощью cons
оператора с
Nil в качестве последнего элемента
1
. Например, использо
вание следующего способа инициализации переменной
OneTwoThree даст ей то же значение, что и в предыдущем подходе
List(1,
2,
3)
:
val oneTwoThree = 1 :: 2 :: 3 :: Nil
Имеющийся в Scala класс
List укомплектован весьма полезными методами, многие из которых показаны в табл. 3.1. Вся эффективность списков будет раскрыта в главе 14.
1
Причина, по которой в конце списка нужен
Nil
, заключается в том, что метод
::
определен в классе
List
. Если попытаться просто воспользоваться кодом
1
::
2
::
3
, то он не пройдет компиляцию, поскольку
3
относится к типу
Int
, у которого нет метода
::.
76 Глава 3 • Дальнейшие шаги в Scala
Тонкости добавления в списки
Класс
List реализует операцию добавления в список с помощью команды
:+
. Подробнее об этом — в главе 24. Однако эта операция используется редко, поскольку время, необходимое для добавления элемента в список, увеличивается в соответствии с размером списка, а время при добавлении методом
::
фиксированное и не зависит от размера списка. Если вы хотите эффективно работать со списками, то добавляйте элементы в начало, а в конце вызовите reverse
. В против
ном случае вы можете использовать
ListBuffer
— изменяемый список, который реализует операцию добавления, а после ее окончания вы
зовите toList
ListBuffer будет описан в разделе 15.1.
Таблица 3.1. Некоторые методы класса List и их использование
Что используется
Что этот метод делает
List.empty или Nil
Создает пустой список
List
List("Cool", "tools", "rule")
Создает новый список типа
List[String]
с тремя значениями:
"Cool"
,
"tools"
и "rule"
val thrill = "Will" :: "fill" ::
"until" :: Nil
Создает новый список типа
List[String]
с тремя значениями:
"Will"
,
"fill"
и "until"
List("a", "b") ::: List("c", "d")
Объединяет два списка (возвращает новый список типа
List[String]
со значениями "a"
,
"b"
,
"c"
и "d"
)
thrill(2)
Возвращает элемент с индексом 2 (при начале отсчета с нуля) списка thrill
(воз
вращает "until"
)
thrill.count(s => s.length == 4)
Подсчитывает количество строковых элементов в thrill
, имеющих длину 4 (воз
вращает 2)
thrill.drop(2)
Возвращает список thrill без его первых двух элементов (возвращает
List("until")
)
thrill.dropRight(2)
Возвращает список thrill без двух крайних справа элементов (возвращает
List("Will")
)
thrill.exists(s => s == "until")
Определяет наличие в списке thrill строкового элемента, имеющего значение "until"
(возвращает true
)
Шаг 8 . Используем списки 77
Что используется
Что этот метод делает
thrill.filter(s => s.length == 4)
Возвращает список всех элементов списка thrill
, имеющих длину 4, соблюдая по
рядок их следования в списке (возвращает
List("Will"
,
"fill")
)
thrill.forall(s => s.endsWith("l"))
Показывает, заканчиваются ли все элемен
ты в списке thrill буквой "l"
(возвращает true
)
thrill.foreach(s => print(s))
Выполняет инструкцию print в отноше
нии каждой строки в списке thrill
(выво
дит "Willfilluntil"
)
thrill.foreach(print)
Делает то же самое, что и предыдущий код, но с использованием более лаконич
ной формы записи (также выводит "Willfilluntil"
)
thrill.head
Возвращает первый элемент в списке thrill
(возвращает "Will"
)
thrill.init
Возвращает список всех элементов списка thrill
, кроме последнего (возвращает
List("Will", "fill")
)
thrill.isEmpty
Показывает, не пуст ли список thrill
(воз
вращает false
)
thrill.last
Возвращает последний элемент в списке thrill
(возвращает "until"
)
thrill.length
Возвращает количество элементов в спи
ске thrill
(возвращает 3)
thrill.map(s => s + "y")
Возвращает список, который получается в результате добавления "y"
к каждому строковому элементу в списке thrill
(возвращает
List("Willy", "filly",
"untily")
)
thrill.mkString(", ")
Создает строку с элементами списка (воз
вращает "Will,
fill,
until"
)
thrill.filterNot(s => s.length
== 4)
Возвращает список всех элементов в по
рядке их следования в списке thrill
, за ис
ключением имеющих длину 4 (возвращает
List("until")
)
thrill.reverse
Возвращает список, содержащий все элементы списка thrill
, следующие в об
ратном порядке (возвращает
List("until",
"fill",
"Will")
)
78 Глава 3 • Дальнейшие шаги в Scala
Что используется
Что этот метод делает
thrill.sortWith((s, t)
=> s.charAt(0).toLower < t.charAt(0).toLower)
Возвращает список, содержащий все эле
менты списка thrill в алфавитном поряд
ке с первым символом, преобразованным в символ нижнего регистра (возвращает
List("fill",
"until",
"will")
)
thrill.tail
Возвращает список thrill за исключе
нием его первого элемента (возвращает
List("fill",
"until")
)
Шаг 9 . Используем кортежи
Еще один полезный объектконтейнер — кортеж. Как и списки, кортежи не могут быть изменены, но, в отличие от списков, могут содержать различные типы элементов. Список может быть типа
List[Int]
или
List[String]
, а кор
теж может содержать одновременно как целые числа, так и строки. Кортежи находят широкое применение, например, при возвращении из метода сразу нескольких объектов. Там, где на Java для хранения нескольких возвраща
емых значений зачастую приходится создавать JavaBeanподобный класс, в Scala можно просто вернуть кортеж. Все делается просто: чтобы создать экземпляр нового кортежа, содержащего объекты, нужно лишь заключить объекты в круглые скобки, отделив их друг от друга запятыми. Создав экземпляр кортежа, вы можете получить доступ к его элементам по отдель
ности с помощью нулевого индекса в круглых скобках. Пример показан в листинге 3.4.
Листинг 3.4. Создание и использование кортежа val pair = (99, "Luftballons")
val num = pair(0) // тип Int, значение 99
val what = pair(1) // тип String, значение "Luftballons"
В первой строке листинга 3.4 создается новый кортеж, содержащий в ка
честве первого элемента целочисленное значение
99
, а в качестве второ
го — строку "Luftballons"
. Scala выводит тип кортежа в виде
Tuple2[Int,
String]
, а также присваивает этот тип паре переменных
1
. Во второй строке
1
Компилятор Scala использует синтаксический сахар для типов кортежей, который выглядит как кортеж типов. Например,
Tuple2
[Int,
String]
представлен как
(Int,
String)
Таблица 3.1 (окончание)
Шаг 10 . Используем множества и отображения 79
вы получаете доступ к первому элементу
99
по его индексу 0 1
. Результатом типа pair(0)
является
Int
. В третьей строке вы получаете доступ ко второму элементу
Luftballons по его индексу 1. Результатом типа pair(1)
является
String
. Это говорит о том, что кортежи отслеживают индивидуальные типы каждого из своих элементов.
Реальный тип кортежа зависит от количества содержащихся в нем эле
ментов и от типов этих элементов. Следовательно, типом кортежа
(99,
"Luftballons")
является
Tuple2[Int,
String]
. А типом кортежа
('u',
'r',
"the",
1,
4,
"me")
—
Tuple6[Char,
Char,
String,
Int,
Int,
String]
2
Шаг 10 . Используем множества и отображения
Scala призван помочь вам использовать преимущества как функциональ
ного, так и объектноориентированного стиля, поэтому в библиотеках его коллекций особое внимание обращают на разницу между изменяемыми и не
изменяемыми коллекциями. Например, массивы всегда изменяемы, а списки всегда неизменяемы. Scala также предоставляет изменяемые и неизменяемые альтернативы для множеств и отображений, но использует для обеих версий одни и те же простые имена. Для множеств и отображений Scala моделирует изменяемость в иерархии классов.
Например, в API Scala содержится основной трейт для множеств, где этот трейт аналогичен Javaинтерфейсу (более подробно трейты рассматриваются в главе 11). Затем Scala предоставляет два трейтанаследника: один для из
меняемых, а второй для неизменяемых множеств.
На рис. 3.2 показано, что для всех трех трейтов используется одно и то же простое имя
Set
. Но их полные имена отличаются друг от друга, поскольку все трейты размещаются в разных пакетах. Классы для конкретных множеств в Scala API, например
HashSet
(см. рис. 3.2), являются расширениями либо изменяемого, либо неизменяемого трейта
Set
. (В то время как в Java вы реализуете интерфейсы, в Scala расширяете (иначе говоря, подмешиваете) трейты.) Следовательно, если нужно воспользоваться
HashSet
, то в зависимо
сти от потребностей можно выбирать между его изменяемой и неизменяемой
1
Обратите внимание, что до Scala 3 обращение к элементам кортежа осуществлялось с помощью имен полей, начинающихся с единицы, например
_1
или
_2 2
Как и в Scala 3, вы можете создавать кортежи любой длины.
80 Глава 3 • Дальнейшие шаги в Scala разновидностями. Способ создания множества по умолчанию показан в ли
стинге 3.5.
Листинг 3.5. Создание, инициализация и использование неизменяемого множества var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
val query = jetSet.contains("Cessna") // false
В первой строке кода листинга 3.5 определяется новая var
переменная по имени jetSet
, которая инициализируется неизменяемым множеством, содер
жащим две строки:
"Boeing"
и "Airbus"
. В этом примере показано, что в Scala множества можно создавать точно так же, как списки и массивы: путем вызо
ва фабричного метода по имени apply в отношении объектакомпаньона
Set
В листинге 3.5 метод apply вызывается в отношении объектакомпаньона для scala.collection.immutable.Set
, возвращающего экземпляр исходного, не
изменяемого класса
Set
. Компилятор Scala выводит тип переменной jetSet
, определяя его как неизменяемый
Set[String]
Рис. 3.2. Иерархия классов для множеств Scala
Чтобы добавить новый элемент в неизменяемое множество, в отношении последнего вызывается метод
+
, которому и передается этот элемент. Ме
тод
+
создает и возвращает новое неизменяемое множество с добавленным элементом. Конкретный метод
+=
предоставляется исключительно для из
меняемых множеств.
Шаг 10 . Используем множества и отображения
Следовательно, код greetStrings(i)
преобразуется в код greetStrings.
apply(i)
. Получается, что элемент массива в Scala является просто вызовом обычного метода, ничем не отличающегося от любого своего собрата. Этот принцип не ограничивается массивами: любое использование объекта в от
ношении какихлибо аргументов в круглых скобках будет преобразовано в вызов метода apply
. Разумеется, данный код будет скомпилирован, только если в этом типе объекта определен метод apply
. То есть это не особый случай, а общее правило.
Рис. 3.1. Все операции в Scala являются вызовами методов
По аналогии с этим, когда присваивание выполняется в отношении пере
менной, к которой применены круглые скобки с одним или несколькими аргументами внутри, компилятор выполнит преобразование в вызов метода update
, получающего не только аргументы в круглых скобках, но и объект, расположенный справа от знака равенства. Например, код greetStrings(0) = "Hello"
будет преобразован в код greetStrings.update(0, "Hello")
Таким образом, следующий код семантически эквивалентен коду листин
га 3.1:
val greetStrings = new Array[String](3)
greetStrings.update(0, "Hello")
greetStrings.update(1, ", ")
greetStrings.update(2, "world!\n")
for i <- 0.to(2) do print(greetStrings.apply(i))
Шаг 8 . Используем списки 73
Концептуальная простота в Scala достигается за счет того, что все — от массивов до выражений — рассматривается как объекты с методами. Вам не нужно запоминать особые случаи, например такие, как существующее в Java различие между примитивными типами и соответствующими им типамиоболочками или между массивами и обычными объектами. Бо
лее того, подобное единообразие не вызывает больших потерь произво
дительности. Компилятор Scala везде, где только возможно, использует в скомпилированном коде массивы Java, элементарные типы и чистые арифметические операции.
Рассмотренные до сих пор в этом шаге примеры компилируются и выпол
няются весьма неплохо, однако в Scala имеется более лаконичный способ создания и инициализации массивов, который, как правило, вы и будете ис
пользовать (см. листинг 3.2). Данный код создает новый массив длиной три элемента, инициализируемый переданными строками "zero"
,
"one"
и "two"
Компилятор выводит тип массива как
Array[String]
, поскольку ему пере
даются строки.
Листинг 3.2. Создание и инициализация массива val numNames = Array("zero", "one", "two")
Фактически в листинге 3.2 вызывается фабричный метод по имени apply
, создающий и возвращающий новый массив. Метод apply получает пере
менное количество аргументов
1
и определяется в объекте-компаньоне
Array
Подробнее объектыкомпаньоны будут рассматриваться в разделе 4.3. Если вам приходилось программировать на Java, то можете воспринимать это как вызов статического метода по имени apply в отношении класса
Array
. Менее лаконичный способ вызова того же метода apply выглядит следующим об
разом:
val numNames2 = Array.apply("zero", "one", "two")
Шаг 8 . Используем списки
Одна из превосходных отличительных черт функционального стиля про
граммирования — полное отсутствие у методов побочных эффектов. Един
ственным действием метода должно быть вычисление и возвращение зна
чения. Получаемые в результате применения такого подхода преимущества
1
Списки аргументов переменной длины или повторяемые параметры рассматрива
ются в разделе 8.8.
74 Глава 3 • Дальнейшие шаги в Scala заключаются в том, что методы становятся менее запутанными, и это упро
щает их чтение и повторное использование. Есть и еще одно преимущество
(в статически типизированных языках): все попадающее в метод и выходя
щее за его пределы проходит проверку на принадлежность к определенному типу, поэтому логические ошибки, скорее всего, проявятся сами по себе в виде ошибок типов. Применять данную функциональную философию к миру объектов означает превратить эти объекты в неизменяемые.
Как вы уже видели, массив Scala — неизменяемая последовательность объ
ектов с общим типом. Тип
Array[String]
, к примеру, содержит только строки.
Изменить длину массива после создания его экземпляра невозможно, но вы можете изменять значения его элементов. Таким образом, массивы относятся к изменяемым объектам.
Для неизменяемой последовательности объектов с общим типом можно воспользоваться списком, определяемым Scalaклассом
List
. Как и в случае применения массивов, в типе
List[String]
содержатся только строки. Спи
сок Scala
List отличается от Javaтипа java.util.List тем, что списки Scala всегда неизменямые, а списки Java могут изменяться. В более общем смысле список Scala разработан с прицелом на использование функционального стиля программирования. Список создается очень просто, и листинг 3.3 как раз показывает это.
Листинг 3.3. Создание и инициализация списка val oneTwoThree = List(1, 2, 3)
Код в листинге 3.3 создает новую val
переменную по имени oneTwoThree
, инициализируемую новым списком
List[Int]
с целочисленными элемента
ми
1
,
2
и
3 1
. Изза своей неизменяемости списки ведут себя подобно строкам в Java: при вызове метода в отношении списка изза имени данного метода может создаваться впечатление, что обрабатываемый список будет изменен, но вместо этого создается и возвращается новый список с новым значением.
Например, в
List для объединения списков имеется метод, обозначаемый как
:::
. Используется он следующим образом:
val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour
1
Использовать запись new List не нужно, поскольку
List.apply()
определен в объ
ектекомпаньоне scala.List как фабричный метод. Более подробно объектыком
паньоны рассматриваются в разделе 4.3.
Шаг 8 . Используем списки 75
После выполнения этого кода oneTwoThreeFour будет ссылаться на
List(1,
2,
3,
4)
, но oneTwo попрежнему будет ссылаться на
List(1,
2)
, а threeFour
— на
List(3,
4)
. Ни один из списков операндов не изменяется оператором кон
катенации
:::
, который возвращает новый список со значением
List(1,
2,
3,
4)
. Возможно, работая со списками, вы чаще всего будете пользоваться оператором
::
, который называется cons cons добавляет новый элемент в на
чало существующего списка и возвращает полученный список. Например, если вы запустите этот код: val twoThree = List(2, 3)
val oneTwoThree = 1 :: twoThree значение oneTwoThree будет
List(1,
2,
3)
ПРИМЕЧАНИЕ
В выражении 1 :: twoThree метод :: относится к правому операнду — списку twoThree . Можно заподозрить, будто с ассоциативностью метода :: что-то не то, но есть простое мнемоническое правило: если метод используется в виде оператора, например a * b, то вызывается в отношении левого операнда, как в выражении a .*(b), если только имя метода не закан- чивается двоеточием . А если оно заканчивается двоеточием, то метод вызывается в отношении правого операнда . Поэтому в выражении 1 :: twoThree метод :: вызывается в отношении twoThree с передачей ему 1, то есть twoThree .::(1) . Ассоциативность операторов более подробно будет рассматриваться в разделе 5 .9 .
Исходя из того, что короче всего указать пустой список с помощью
Nil
, один из способов инициализировать новые списки — связать элементы с помощью cons
оператора с
Nil в качестве последнего элемента
1
. Например, использо
вание следующего способа инициализации переменной
OneTwoThree даст ей то же значение, что и в предыдущем подходе
List(1,
2,
3)
:
val oneTwoThree = 1 :: 2 :: 3 :: Nil
Имеющийся в Scala класс
List укомплектован весьма полезными методами, многие из которых показаны в табл. 3.1. Вся эффективность списков будет раскрыта в главе 14.
1
Причина, по которой в конце списка нужен
Nil
, заключается в том, что метод
::
определен в классе
List
. Если попытаться просто воспользоваться кодом
1
::
2
::
3
, то он не пройдет компиляцию, поскольку
3
относится к типу
Int
, у которого нет метода
::.
76 Глава 3 • Дальнейшие шаги в Scala
Тонкости добавления в списки
Класс
List реализует операцию добавления в список с помощью команды
:+
. Подробнее об этом — в главе 24. Однако эта операция используется редко, поскольку время, необходимое для добавления элемента в список, увеличивается в соответствии с размером списка, а время при добавлении методом
::
фиксированное и не зависит от размера списка. Если вы хотите эффективно работать со списками, то добавляйте элементы в начало, а в конце вызовите reverse
. В против
ном случае вы можете использовать
ListBuffer
— изменяемый список, который реализует операцию добавления, а после ее окончания вы
зовите toList
ListBuffer будет описан в разделе 15.1.
Таблица 3.1. Некоторые методы класса List и их использование
Что используется
Что этот метод делает
List.empty или Nil
Создает пустой список
List
List("Cool", "tools", "rule")
Создает новый список типа
List[String]
с тремя значениями:
"Cool"
,
"tools"
и "rule"
val thrill = "Will" :: "fill" ::
"until" :: Nil
Создает новый список типа
List[String]
с тремя значениями:
"Will"
,
"fill"
и "until"
List("a", "b") ::: List("c", "d")
Объединяет два списка (возвращает новый список типа
List[String]
со значениями "a"
,
"b"
,
"c"
и "d"
)
thrill(2)
Возвращает элемент с индексом 2 (при начале отсчета с нуля) списка thrill
(воз
вращает "until"
)
thrill.count(s => s.length == 4)
Подсчитывает количество строковых элементов в thrill
, имеющих длину 4 (воз
вращает 2)
thrill.drop(2)
Возвращает список thrill без его первых двух элементов (возвращает
List("until")
)
thrill.dropRight(2)
Возвращает список thrill без двух крайних справа элементов (возвращает
List("Will")
)
thrill.exists(s => s == "until")
Определяет наличие в списке thrill строкового элемента, имеющего значение "until"
(возвращает true
)
Шаг 8 . Используем списки 77
Что используется
Что этот метод делает
thrill.filter(s => s.length == 4)
Возвращает список всех элементов списка thrill
, имеющих длину 4, соблюдая по
рядок их следования в списке (возвращает
List("Will"
,
"fill")
)
thrill.forall(s => s.endsWith("l"))
Показывает, заканчиваются ли все элемен
ты в списке thrill буквой "l"
(возвращает true
)
thrill.foreach(s => print(s))
Выполняет инструкцию print в отноше
нии каждой строки в списке thrill
(выво
дит "Willfilluntil"
)
thrill.foreach(print)
Делает то же самое, что и предыдущий код, но с использованием более лаконич
ной формы записи (также выводит "Willfilluntil"
)
thrill.head
Возвращает первый элемент в списке thrill
(возвращает "Will"
)
thrill.init
Возвращает список всех элементов списка thrill
, кроме последнего (возвращает
List("Will", "fill")
)
thrill.isEmpty
Показывает, не пуст ли список thrill
(воз
вращает false
)
thrill.last
Возвращает последний элемент в списке thrill
(возвращает "until"
)
thrill.length
Возвращает количество элементов в спи
ске thrill
(возвращает 3)
thrill.map(s => s + "y")
Возвращает список, который получается в результате добавления "y"
к каждому строковому элементу в списке thrill
(возвращает
List("Willy", "filly",
"untily")
)
thrill.mkString(", ")
Создает строку с элементами списка (воз
вращает "Will,
fill,
until"
)
thrill.filterNot(s => s.length
== 4)
Возвращает список всех элементов в по
рядке их следования в списке thrill
, за ис
ключением имеющих длину 4 (возвращает
List("until")
)
thrill.reverse
Возвращает список, содержащий все элементы списка thrill
, следующие в об
ратном порядке (возвращает
List("until",
"fill",
"Will")
)
78 Глава 3 • Дальнейшие шаги в Scala
Что используется
Что этот метод делает
thrill.sortWith((s, t)
=> s.charAt(0).toLower < t.charAt(0).toLower)
Возвращает список, содержащий все эле
менты списка thrill в алфавитном поряд
ке с первым символом, преобразованным в символ нижнего регистра (возвращает
List("fill",
"until",
"will")
)
thrill.tail
Возвращает список thrill за исключе
нием его первого элемента (возвращает
List("fill",
"until")
)
Шаг 9 . Используем кортежи
Еще один полезный объектконтейнер — кортеж. Как и списки, кортежи не могут быть изменены, но, в отличие от списков, могут содержать различные типы элементов. Список может быть типа
List[Int]
или
List[String]
, а кор
теж может содержать одновременно как целые числа, так и строки. Кортежи находят широкое применение, например, при возвращении из метода сразу нескольких объектов. Там, где на Java для хранения нескольких возвраща
емых значений зачастую приходится создавать JavaBeanподобный класс, в Scala можно просто вернуть кортеж. Все делается просто: чтобы создать экземпляр нового кортежа, содержащего объекты, нужно лишь заключить объекты в круглые скобки, отделив их друг от друга запятыми. Создав экземпляр кортежа, вы можете получить доступ к его элементам по отдель
ности с помощью нулевого индекса в круглых скобках. Пример показан в листинге 3.4.
Листинг 3.4. Создание и использование кортежа val pair = (99, "Luftballons")
val num = pair(0) // тип Int, значение 99
val what = pair(1) // тип String, значение "Luftballons"
В первой строке листинга 3.4 создается новый кортеж, содержащий в ка
честве первого элемента целочисленное значение
99
, а в качестве второ
го — строку "Luftballons"
. Scala выводит тип кортежа в виде
Tuple2[Int,
String]
, а также присваивает этот тип паре переменных
1
. Во второй строке
1
Компилятор Scala использует синтаксический сахар для типов кортежей, который выглядит как кортеж типов. Например,
Tuple2
[Int,
String]
представлен как
(Int,
String)
Таблица 3.1 (окончание)
Шаг 10 . Используем множества и отображения 79
вы получаете доступ к первому элементу
99
по его индексу 0 1
. Результатом типа pair(0)
является
Int
. В третьей строке вы получаете доступ ко второму элементу
Luftballons по его индексу 1. Результатом типа pair(1)
является
String
. Это говорит о том, что кортежи отслеживают индивидуальные типы каждого из своих элементов.
Реальный тип кортежа зависит от количества содержащихся в нем эле
ментов и от типов этих элементов. Следовательно, типом кортежа
(99,
"Luftballons")
является
Tuple2[Int,
String]
. А типом кортежа
('u',
'r',
"the",
1,
4,
"me")
—
Tuple6[Char,
Char,
String,
Int,
Int,
String]
2
Шаг 10 . Используем множества и отображения
Scala призван помочь вам использовать преимущества как функциональ
ного, так и объектноориентированного стиля, поэтому в библиотеках его коллекций особое внимание обращают на разницу между изменяемыми и не
изменяемыми коллекциями. Например, массивы всегда изменяемы, а списки всегда неизменяемы. Scala также предоставляет изменяемые и неизменяемые альтернативы для множеств и отображений, но использует для обеих версий одни и те же простые имена. Для множеств и отображений Scala моделирует изменяемость в иерархии классов.
Например, в API Scala содержится основной трейт для множеств, где этот трейт аналогичен Javaинтерфейсу (более подробно трейты рассматриваются в главе 11). Затем Scala предоставляет два трейтанаследника: один для из
меняемых, а второй для неизменяемых множеств.
На рис. 3.2 показано, что для всех трех трейтов используется одно и то же простое имя
Set
. Но их полные имена отличаются друг от друга, поскольку все трейты размещаются в разных пакетах. Классы для конкретных множеств в Scala API, например
HashSet
(см. рис. 3.2), являются расширениями либо изменяемого, либо неизменяемого трейта
Set
. (В то время как в Java вы реализуете интерфейсы, в Scala расширяете (иначе говоря, подмешиваете) трейты.) Следовательно, если нужно воспользоваться
HashSet
, то в зависимо
сти от потребностей можно выбирать между его изменяемой и неизменяемой
1
Обратите внимание, что до Scala 3 обращение к элементам кортежа осуществлялось с помощью имен полей, начинающихся с единицы, например
_1
или
_2 2
Как и в Scala 3, вы можете создавать кортежи любой длины.
80 Глава 3 • Дальнейшие шаги в Scala разновидностями. Способ создания множества по умолчанию показан в ли
стинге 3.5.
Листинг 3.5. Создание, инициализация и использование неизменяемого множества var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
val query = jetSet.contains("Cessna") // false
В первой строке кода листинга 3.5 определяется новая var
переменная по имени jetSet
, которая инициализируется неизменяемым множеством, содер
жащим две строки:
"Boeing"
и "Airbus"
. В этом примере показано, что в Scala множества можно создавать точно так же, как списки и массивы: путем вызо
ва фабричного метода по имени apply в отношении объектакомпаньона
Set
В листинге 3.5 метод apply вызывается в отношении объектакомпаньона для scala.collection.immutable.Set
, возвращающего экземпляр исходного, не
изменяемого класса
Set
. Компилятор Scala выводит тип переменной jetSet
, определяя его как неизменяемый
Set[String]
Рис. 3.2. Иерархия классов для множеств Scala
Чтобы добавить новый элемент в неизменяемое множество, в отношении последнего вызывается метод
+
, которому и передается этот элемент. Ме
тод
+
создает и возвращает новое неизменяемое множество с добавленным элементом. Конкретный метод
+=
предоставляется исключительно для из
меняемых множеств.
Шаг 10 . Используем множества и отображения
1 ... 5 6 7 8 9 10 11 12 ... 64