Главная страница
Навигация по странице:

  • Листинг 3.2.

  • Листинг 3.3.

  • Таблица 3.1.

  • Листинг 3.4.

  • Таблица 3.1

  • Scala. Профессиональное программирование 2022. Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста


    Скачать 6.24 Mb.
    НазваниеОдерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста
    Дата27.04.2023
    Размер6.24 Mb.
    Формат файлаpdf
    Имя файлаScala. Профессиональное программирование 2022.pdf
    ТипДокументы
    #1094967
    страница9 из 64
    1   ...   5   6   7   8   9   10   11   12   ...   64
    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 . Используем множества и отображения
    1   ...   5   6   7   8   9   10   11   12   ...   64


    написать администратору сайта