Файл: Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.12.2023
Просмотров: 756
Скачиваний: 11
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
23 .6 . Пример использования класса типов: сериализация JSON 509
Результат в формате JSON показан в листинге 23.22.
Листинг 23.21. AddressBook val addressBook =
AddressBook(
List(
Contact(
"Bob Smith",
List(
Address(
"12345 Main Street",
"San Francisco",
"CA",
94105
),
Address(
"500 State Street",
"Los Angeles",
"CA",
90007
)
),
List(
Phone(
1,
5558881234
),
Phone(
49,
5558413323
)
)
)
)
)
Листинг 23.22. Адресная книга, представленная в формате JSON
{
"contacts": [{
"name": "Bob Smith",
"addresses": [{
"street": "12345 Main Street",
"city": "San Francisco",
"state": "CA",
"zip": 94105
}, {
"street": "500 State Street",
"city": "Los Angeles",
"state": "CA",
510 Глава 23 • Классы типов "zip": 90007
}],
"phones": [{
"countryCode": 1,
"phoneNumber": 5558881234
}, {
"countryCode": 49,
"phoneNumber": 5558413323
}]
}]
}
Конечно, настоящая библиотека для работы с JSON была бы намного слож
нее того, что вы увидели в этом примере. Вам, скорее всего, следовало бы вос
пользоваться средствами метапрограммирования Scala, чтобы автоматизи
ровать генерацию экземпляров
JsonSerializer за счет вывода класса типов.
Резюме
В этой главе вы познакомились с классами типов и рассмотрели несколько примеров. Классы типов — это основополагающий способ реализации спе
циального полиморфизма в Scala. Тот факт, что для классов типов в Scala предусмотрен синтаксический сахар в виде границ контекста, говорит о том, насколько важным является данный подход к проектированию в этом языке.
Вам встречалось несколько способов применения классов типов: для глав
ных методов, безопасных проверок на равенство, неявных преобразований и сериализации JSON. Надеемся, эти примеры дали вам представление о том, в каких ситуациях классы типов являются подходящим архитектур
ным решением. В следующей главе мы сменим тему и подробно рассмотрим библиотеку коллекций Scala.
1 ... 49 50 51 52 53 54 55 56 ... 64
24
Углубленное изучение коллекций
В Scala включена весьма элегантная и эффективная библиотека коллекций.
На первый взгляд API коллекций представляется незаметным, однако вы
званные им изменения в вашем стиле программирования могут быть весьма существенными. Зачастую это похоже на работу на высоком уровне с основ
ными строительными блоками программы, представляющими собой скорее коллекции, чем их элементы. Этот новый стиль программирования требует некоторой адаптации. К счастью, ее облегчает ряд привлекательных свойств коллекций Scala. Они просты в использовании, лаконичны в описании, без
опасны, быстры в работе и универсальны.
z z
Простота использования. Небольшого словаря, содержащего от 20 до 50 методов, вполне достаточно для решения основного набора задач всего за пару операций. Не нужно морочить голову сложными ци
кличными структурами или рекурсиями. Стабильные коллекции и опе
рации без побочных эффектов означают, что вам не следует опасаться случайного повреждения существующих коллекций новыми данными.
Взаимовлияние итераторов и обновления коллекций исключено.
z z
Лаконичность. То, что раньше занимало от одного до нескольких циклов, теперь можно выразить всего одним словом. Можно выпол
нять функциональные операции, задействуя упрощенный синтаксис, и без особых усилий комбинировать операции таким образом, чтобы в результате получалось нечто похожее на обычные алгебраические формулы.
z z
Безопасность. Чтобы разобраться в этом вопросе, нужен определенный опыт. Природа коллекций Scala с их статической типизацией и функци
ональностью означает, что подавляющее большинство потенциальных
512 Глава 24 • Углубленное изучение коллекций ошибок отлавливается еще во время компиляции. Это достигается благо
даря следующему: y
сами операции над коллекциями используются довольно широко, а следовательно, прошли проверку временем; y
использование операций над коллекциями делает ввод и вывод явным в виде параметров функций и результатов;
y эти явные входные и выходные данные являются предметом для ста
тической проверки типов.
Суть заключается в том, что большинство случаев неверного использо
вания кода проявится в виде ошибок типа. И вовсе не редкость, когда программы, состоящие из нескольких сотен строк кода, запускаются с первой же попытки.
z z
Скорость. Операции с коллекциями, имеющиеся в библиотеках, уже настроены и оптимизированы. В результате этого использование кол
лекций обычно отличается высокой эффективностью. Тщательно на
строенные структуры данных и операции могут улучшить ситуацию, но в то же время, принимая решения, далекие от оптимальных, можно ее значительно ухудшить. Более того, коллекции были адаптированы под параллельную обработку на многоядерных системах. Параллельно обрабатываемые коллекции поддерживают те же самые операции, что и последовательно обрабатываемые, поэтому изучать новые операции и переписывать код не нужно. Последовательно обрабатываемые кол
лекции можно превратить в параллельно обрабатываемые, просто вы
звав метод par z
z
Универсальность. Коллекции реализуют одни и те же операции над любым типом там, где в этих операциях есть определенный смысл. Сле
довательно, используя сравнительно небольшой словарь операций, вы приобретаете множество возможностей. Например, концептуально строка является последовательностью символов. Следовательно, в коллекциях
Scala строки поддерживают все операции с последовательностями. То же самое справедливо и для массивов.
В этой главе мы даем углубленное описание API имеющихся в Scala классов коллекций с точки зрения их использования. Краткий тур по библиотекам коллекций мы совершили в главе 15. В этой главе нам предстоит поуча
ствовать в более подробном путешествии, в ходе которого мы покажем все классы коллекций и все определенные в них методы, то есть представим все сведения, необходимые для использования коллекций Scala.
24 .1 . Изменяемые и неизменяемые коллекции 513
24 .1 . Изменяемые и неизменяемые коллекции
Как вам уже известно, в целом коллекции в Scala подразделяются на изме
няемые и неизменяемые. Изменяемые могут обновляться или расширяться на месте. Это значит, вы можете изменять, добавлять или удалять элементы коллекции как побочный эффект. Неизменяемые коллекции, напротив, всегда сохраняют неизменный вид. Но все же есть операции, имитирующие добавление, удаление или обновление, но каждая из таких операций воз
вращает новую коллекцию, оставляя старую без изменений.
Все классы коллекций находятся в пакете scala.collection или в одном из его подпакетов: mutable
, immutable и generic
. Большинство классов кол
лекций, востребованных в клиентском коде, существуют в трех вариантах, которые имеют разные характеристики в смысле возможности изменения.
Эти три варианта находятся в пакетах scala.collection
, scala.collecti- on.immutable и scala.collection.mutable
Коллекция в пакете scala.collection.immutable гарантированно неизменя
емая для всех. Такая коллекция после создания всегда остается неизменной.
Поэтому можно полагаться на то, что многократные обращения к одному и тому же значению коллекции в разные моменты времени всегда будут давать коллекцию с теми же элементами.
Коллекция в пакете scala.collection.mutable известна тем, что имеет ряд операций, изменяющих коллекцию на месте. Эти операции позволяют вам создавать код для самостоятельного изменения коллекции. Но при этом нужно четко понимать, что существует вероятность обновления из других частей исходного кода, и защищаться от нее.
Коллекции в пакете scala.collection могут быть как изменяемыми, так и неизменяемыми. Например, scala.collection.IndexedSeq[T]
является супертрейтом как для scala.collection.immutable.IndexedSeq[T]
, так и для его изменяемого брата scala.collection.mutable.IndexedSeq[T]
. В целом корневые коллекции в пакете scala.collection поддерживают трансфор
мирующие операции, которые влияют на целую коллекцию, такие как map и filter
. Неизменяемые коллекции в пакете scala.collection.immutable обычно дополняют операциями добавления и удаления отдельных значений, а изменяемые коллекции в пакете scala.collection.mutable дополняются некоторыми модифицирующими операциями с побочными эффектами.
Между корневыми и неизменяемыми коллекциями есть еще одно различие: клиенты неизменяемых коллекций получают гарантии того, что никто не
514 Глава 24 • Углубленное изучение коллекций сможет внести в коллекцию изменения, а клиенты корневых коллекций знают только то, что не могут изменить коллекцию самостоятельно. Даже если статический тип такой коллекции не предоставляет операций для ее изменения, все же существует вероятность того, что в процессе выполнения программы она получит тип изменяемой коллекции, предоставив другим клиентам возможность вносить в нее изменения.
По умолчанию в Scala всегда выбираются неизменяемые коллекции. На
пример, если просто написать
Set без какоголибо префикса или ничего не импортируя, то будет получено неизменяемое множество, а если написать
Iterable
— то неизменяемый объект с возможностью обхода его элементов, поскольку имеются привязки по умолчанию, импортируемые из пакета scala
. Чтобы получить изменяемые версии по умолчанию, следует явно указать collection.mutable.Set или collection.mutable.Iterable
Последний пакет в иерархии коллекций — collection.generic
. В нем со
держатся строительные блоки для абстрагирования поверх конкретных кол
лекций. Впрочем, постоянные пользователи коллекций должны ссылаться на классы в пакете generic только в крайних случаях.
24 .2 . Согласованность коллекций
Наиболее важные классы коллекций показаны на рис. 24.1.
Эти классы имеют много общего. Например, каждая разновидность кол
лекции может быть создана с использованием единообразного синтаксиса, заключающегося в записи названия класса коллекции, за которым стоят элементы данной коллекции:
Iterable("x", "y", "z")
Map("x" –> 24, "y" –> 25, "z" –> 26)
Set(Color.Red, Color.Green, Color.Blue)
SortedSet("hello", "world")
Buffer(x, y, z)
IndexedSeq(1.0, 2.0)
LinearSeq(a, b, c)
Тот же принцип применяется и к конкретным реализациям коллекций:
List(1, 2, 3)
HashMap("x" –> 24, "y" –> 25, "z" –> 26)
Методы toString для всех коллекций выводят информацию, аналогичную показанной ранее, с именем типа, за которым следуют значения элементов
24 .2 . Согласованность коллекций 515
Iterable
Seq
IndexedSeq
ArraySeq
Vector
ArrayDeque (mutable)
Queue (mutable)
Stack (mutable)
Range
NumericRange
LinearSeq
List
LazyList
Queue (immutable)
Buffer
ListBuffer
ArrayBuffer
Set
SortedSet
TreeSet
HashSet (mutable)
LinkedHashSet
HashSet (immutable)
BitSet
EmptySet, Set1, Set2, Set3, Set4
Map
SortedMap
TreeMap
HashMap (mutable)
LinkedHashMap (mutable)
HashMap (immutable)
VectorMap (immutable)
EmptyMap, Map1, Map2, Map3, Map4
Рис. 24.1. Иерархия коллекций коллекции, заключенные в круглые скобки. Все коллекции поддерживают
API, предоставляемый
Iterable
, но все их методы возвращают собственный класс, а не корневой класс
Iterable
. Например, у метода map класса
List тип возвращаемого значения
List
, у метода map класса
Set тип возвращаемого значения
Set
. То есть статический возвращаемый тип этих методов абсо
лютно точен:
List(1, 2, 3).map(_ + 1) // List(2, 3, 4): List[Int]
Set(1, 2, 3).map(_ * 2) // Set(2, 4, 6): Set[Int]
516 Глава 24 • Углубленное изучение коллекций
Равенство для всех классов коллекций также организовано единообразно: более подробно этот вопрос рассматривается в разделе 24.12.
Большинство классов, показанных на рис. 24.1, существуют в трех вари
антах: корневом, изменяемом и неизменяемом (
root
, mutable и immutable
).
Единственное исключение — трейт
Buffer
, существующий только в виде изменяемой коллекции.
Далее в главе все эти классы мы рассмотрим поочередно.
24 .3 . Трейт Iterable
На вершине иерархии коллекций находится трейт
Iterable[A]
, где
A
— тип элементов коллекции. Все методы в этом трейте определены в терминах абстрактного метода iterator
, который возвращает элементы коллекции один за другим.
def iterator: Iterator[A]
Классам коллекций, реализующим
Iterable
, нужно определить только этот метод, а все остальные методы могут быть унаследованы из
Iterable
В
Iterable также определяется много конкретных методов, перечисленных в табл. 24.1. Их можно разбить на следующие категории.
z z
Операции итерирования foreach, grouped, sliding перебирают все эле
менты коллекции в порядке, который определяет ее итератор. Мето
ды grouped и sliding возвращают итераторы, которые, в свою очередь, возвращают не отдельные элементы, а, скорее, подпоследовательности элементов исходной коллекции. Максимальный размер этих подпоследо
вательностей указывается в качестве аргумента для этих методов. Метод grouped делит полученные элементы на инкременты, а sliding формирует по ним скользящее окно. Разницу между ними должен наглядно проде
монстрировать следующий код:
val xs = List(1, 2, 3, 4, 5)
val git = xs.grouped(3) // an Iterator[List[Int]]
git.next() // List(1, 2, 3)
git.next() // List(4, 5)
val sit = xs.sliding(3) // an Iterator[List[Int]]
sit.next() // List(1, 2, 3)
sit.next() // List(2, 3, 4)
sit.next() // List(3, 4, 5)
24 .3 . Трейт Iterable 517
z z
Сложение ++ (псевдоним: concat
) складывает две коллекции вместе или добавляет все элементы итератора в коллекцию.
z z
Операции отображения map, flatMap и collect создают новую коллекцию путем применения некой функции к элементам коллекции.
z z
Преобразования toIndexedSeq, toIterable, toList, toMap, toSeq, toSet
и toVector превращают коллекцию
Iterable в неизменяемую. Все эти преобразования возвращают объектполучатель, если он уже соответству
ет требуемому типу коллекции. Например, применение toList к списку выдаст сам список. Методы toArray и toBuffer возвращают новую изме
няемую коллекцию, даже если объектполучатель соответствует. Метод to можно использовать для преобразования в любую другую коллекцию.
z z
Операция копирования copyToArray. Как следует из названия, копирует элементы коллекции в массив.
z z
Операции с размером isEmpty, nonEmpty, size, knownSize, sizeCompare
и sizeIs имеют отношение к размеру коллекции. Чтобы вычислить коли
чество элементов коллекции, в некоторых случаях может потребоваться совершить обход, например в случае
List
. В других случаях коллек
ция может содержать бесконечное количество элементов, например
LazyList.from(0)
. Методы knownSize
, sizeCompare и sizeIs предостав
ляют информацию о количестве элементов , обходя как можно меньшее количество элементов.
z z
Операции извлечения элементов head, last, headOption, lastOption
и find выбирают первый или последний элемент коллекции или же пер
вый элемент, соответствующий условию. Однако следует заметить, что не все коллекции имеют четко определенное значение первого и послед
него. Например,
HashSet может хранить элементы в соответствии с их хешключами, и порядок их следования от запуска к запуску может изме
няться. В таком случае для разных запусков программы первый элемент
HashSet также может быть разным. Коллекция считается упорядоченной, если всегда выдает свои элементы в одном и том же порядке. Большин
ство коллекций упорядочены, однако некоторые, такие как
HashSet
, таковыми не являются, отказ от упорядочения позволяет им быть более эффективными. Упорядочение зачастую необходимо, чтобы получать вос
производимые тесты и способствовать отладке. Поэтому коллекции Scala предоставляют упорядоченные альтернативы для всех типов коллекций.
Например, упорядоченная альтернатива для
HashSet
—
LinkedHashSet z
z
Операции извлечения подколлекций
takeWhile
, tail
, init
, slice
, take
, drop
, filter
, dropWhile
, filterNot и withFilter возвращают подколлек
цию, определяемую диапазоном индексов или предикатом.