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

  • Листинг 4.3.

  • 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
    страница12 из 64
    1   ...   8   9   10   11   12   13   14   15   ...   64
    100 Глава 4 • Классы и объекты
    С добавлением модификатора case компилятор сгенерирует для вас несколь­
    ко полезных методов. Во­первых, компилятор создаст объект­компаньон и поместит в него фабричный метод с именем apply
    . Таким образом, вы можете создать новый объект
    Person следующим образом:
    val p = Person("Sally", 39)
    Компилятор перепишет эту строку кода в вызов сгенерированного фабрич­
    ного метода:
    Person.apply("Sally",
    39)
    Во­вторых, компилятор будет хранить все параметры класса в полях и ге­
    нерировать методы доступа с тем же именем, что и у заданного параметра
    1
    Например, вы можете получить доступ к заданным в
    Person значениям име­
    ни и возраста следующим образом:
    p.name // Sally p.age // 39
    В­третьих, компилятор предоставит вам реализацию toString
    :
    p.toString // Person(Sally,39)
    В­четвертых, компилятор сгенерирует реализацию hashCode и equals для вашего класса. Эти методы будут основывать свой результат на параметрах, переданных конструктору. Например, объект
    Person будет учитывать и имя, и возраст при сравнении:
    p == Person("Sally", 21) // false p.hashCode == Person("Sally", 21).hashCode // false p == Person("James", 39) // false p.hashCode == Person("James", 39).hashCode // false p == Person("Sally", 39) // true p.hashCode == Person("Sally", 39).hashCode // true
    Компилятор не будет генерировать метод, который вы реализуете самостоя­
    тельно. Он будет использовать вашу реализацию. Вы также можете добавить другие поля и методы к классу и его компаньону. Вот пример, в котором вы определяете метод apply в сопутствующем объекте
    Person
    (компилятор не будет его генерировать) и добавляете метод appendToName в класс:
    case class Person(name: String, age: Int):
    def appendToName(suffix: String): Person =
    Person(s"$name$suffix", age)
    1
    Они называются параметрическими полями, которые будут описаны в разделе 10.6.

    4 .5 . Приложение на языке Scala 101
    object Person:
    // Убедитесь, что непустое имя написано с заглавной буквы def apply(name: String, age: Int): Person =
    val capitalizedName =
    if !name.isEmpty then val firstChar = name.charAt(0).toUpper val restOfName = name.substring(1)
    s"$firstChar$restOfName"
    else throw new IllegalArgumentException("Empty name")
    new Person(capitalizedName, age)
    Этот apply
    ­метод гарантирует, что первый символ имени будет начинаться с заглавной буквы:
    val q = Person("sally", 39) // Person(Sally,39)
    Вы также можете вызвать метод appendToName
    , который вы определили в классе:
    q.appendToName(" Smith") // Person(Sally Smith,39)
    Наконец, компилятор добавляет метод copy в ваш класс и метод unapply к компаньону. Они будут описаны в главе 13.
    Все эти условности облегчают работу, но с небольшой оговоркой: вам всего лишь понадобится написать модификатор case
    , а ваши классы и объекты при этом станут немного больше. Они вырастают, потому что генерируются дополнительные методы и для каждого параметра конструктора добавляется неявное поле.
    4 .5 . Приложение на языке Scala
    Чтобы запустить программу на Scala, нужно предоставить имя автономного объекта­одиночки с методом main
    , который получает один параметр с типом
    Array[String]
    и имеет результирующий тип
    Unit
    . Точкой входа в приложе­
    ние может стать любой самостоятельный объект с методом main
    , имеющим надлежащую сигнатуру
    1
    . Пример показан в листинге 4.3.
    Листинг 4.3. Приложение Summer
    // Код находится в файле Summer.scala import ChecksumAccumulator.calculate
    1
    Вы можете обозначить методы другими именами в качестве основных функций с помощью
    @main
    . Этот метод будет описан в разделе 23.3.

    102 Глава 4 • Классы и объекты object Summer:
    def main(args: Array[String]): Unit =
    for arg <- args do println(arg + ": " + calculate(arg))
    Объект­одиночка, показанный в данном листинге, называется
    Summer
    . Его метод main имеет надлежащую сигнатуру, поэтому его можно задействовать в качестве приложения. Первая инструкция в файле импортирует метод calculate
    , который определен в объекте
    ChecksumAccumulator из предыду­
    щего примера. Инструкция import позволяет далее использовать в файле простое имя метода
    1
    . Тело метода main всего лишь выводит на стандартное устройство каждый аргумент и контрольную сумму для аргумента, разделяя их двоеточием.
    ПРИМЕЧАНИЕ
    Подразумевается, что в каждый свой исходный файл Scala импортирует элементы пакетов java .lang и scala, а также элементы объекта-одиночки по имени Predef . В Predef, который находится в пакете scala, содержится множество полезных методов . Например, когда в исходном файле Scala встречается println, фактически вызывается println из Predef . (А метод
    Predef .println, в свою очередь, вызывает метод Console .println, который фактически и выполняет всю работу .) Когда же встречается assert, вы- зывается метод Predef .assert .
    Чтобы запустить приложение Summer, поместите код из листинга 4.3 в файл
    Summer.scala
    . В Summer используется
    ChecksumAccumulator
    , поэтому поме­
    стите код для
    ChecksumAccumulator как для класса, показанного в листин­
    ге 4.1, так и для его объекта­компаньона, показанного в листинге 4.2, в файл
    ChecksumAccumulator.scala
    Одним из отличий Scala от Java является то, что в Java от вас требуется поме­
    стить публичный класс в файл, названный по имени класса, например, класс
    SpeedRacer
    — в файл
    SpeedRacer.java
    . А в Scala файл с расширением
    .scala можно называть как угодно независимо от того, какие классы Scala или код в них помещаются. Но обычно, когда речь идет не о скриптах, рекоменду­
    ется придерживаться стиля, при котором файлы называются по именам включенных в них классов, как это делается в Java, чтобы программистам было легче искать классы по именам их файлов. Именно этим подходом мы
    1
    Наличие опыта программирования на Java позволяет сопоставить такой импорт с объявлением статического импорта, введенным в Java 5. Единственное отли­
    чие — в Scala импортировать элементы можно из любого объекта, а не только из объектов­одиночек.

    Резюме 103
    и воспользовались в отношении двух файлов в данном примере. Имеются в виду файлы
    Summer.scala и
    ChecksumAccumulator.scala
    Ни
    ChecksumAccumulator.scala
    , ни
    Summer.scala не являются скриптами, поскольку заканчиваются определением. В отличие от этого скрипт должен заканчиваться выражением, выдающим результат. Поэтому при попытке запустить
    Summer.scala в качестве скрипта интерпретатор Scala пожалуется на то, что
    Summer.scala не заканчивается выражением, выдающим результат.
    (Конечно, если предположить, что вы самостоятельно не добавили какое­
    либо выражение после определения объекта
    Summer
    .) Вместо этого нужно будет скомпилировать данные файлы с помощью компилятора Scala, а затем запустить получившиеся в результате файлы классов. Для этого можно вос­
    пользоваться основным компилятором Scala по имени scalac
    :
    $ scalac ChecksumAccumulator.scala Summer.scala
    Эта команда скомпилирует ваши исходные файлы и приведет к созданию файлов классов Java, которые затем можно будет запускать через команду scala
    — ту же самую, с помощью которой вы вызывали интерпретатор в пре­
    дыдущих примерах. Однако вместо того, чтобы указывать ему имя файла с расширением
    .scala
    , содержащим код Scala для интерпретации (как вы делали в каждом предыдущем примере)
    1
    , вы дадите ему имя отдельного объекта, содержащего метод main с соответствующей сигнатурой. Следова­
    тельно, приложение
    Summer можно запустить, набрав команду:
    $ scala Summer of love
    Вы сможете увидеть контрольные суммы, выведенные для двух аргументов командной строки:
    of: -213
    love: -182
    Резюме
    В этой главе мы рассмотрели основы классов и объектов в Scala и показали приемы компиляции и запуска приложений. В следующей главе рассмотрим основные типы данных и варианты их использования.
    1
    Фактический механизм, который программа Scala использует для «интерпрета­
    ции» исходного файла Scala, заключается в том, что она компилирует исходный код Scala в байт­коды Java, немедленно загружает их через загрузчик классов и выполняет их.

    5
    Основные типы и операции
    После того как были рассмотрены в действии классы и объекты, самое время поглубже изучить имеющиеся в Scala основные типы и операции.
    Если вы хорошо знакомы с Java, то вас может обрадовать тот факт, что в Scala и в Java основные типы и операторы имеют тот же смысл. И все же есть интересные различия, ради которых с этой главой стоит ознакомиться даже тем, кто считает себя опытным разработчиком Java­приложений. Не­
    которые аспекты Scala, рассматриваемые в данной главе, в основном такие же, как и в Java, поэтому мы указываем, какие разделы Java­разработчики могут пропустить.
    В текущей главе мы представим обзор основных типов Scala, включая стро­
    ки типа
    String и типы значений
    Int
    ,
    Long
    ,
    Short
    ,
    Byte
    ,
    Float
    ,
    Double
    ,
    Char и
    Boolean
    . Кроме того, рассмотрим операции, которые могут выполняться с этими типами, и вопросы соблюдения приоритета операторов в выражени­
    ях Scala. Поговорим мы и о том, как Scala «обогащает» варианты основных типов, позволяя выполнять дополнительные операции вдобавок к тем, что поддерживаются в Java.
    5 .1 . Некоторые основные типы
    В табл. 5.1 показан ряд основных типов, используемых в Scala, а также диа­
    пазоны значений, которые могут принимать их экземпляры. В совокупности типы
    Byte
    ,
    Short
    ,
    Int
    ,
    Long и
    Char называются целочисленными. Целочислен­
    ные типы плюс
    Float и
    Double называются числовыми.

    5 .2 . Литералы 105
    Таблица 5.1. Некоторые основные типы
    Основной тип
    Диапазон
    Byte
    8­битовое знаковое целое число в дополнительном коде
    (от –2 7
    до 2 7
    – 1 включительно)
    Short
    16­битовое знаковое целое число в дополнительном коде
    (от –2 15
    до 2 15
    – 1 включительно)
    Int
    32­битовое знаковое целое число в дополнительном коде
    (от –2 31
    до 2 31
    – 1 включительно)
    Long
    64­битовое знаковое целое число в дополнительном коде
    (от –2 63
    до 2 63
    – 1 включительно)
    Char
    16­битовый беззнаковый Unicode­символ
    (от 0 до 2 16
    – 1 включительно)
    String
    Последовательность из Char
    Float
    32­битовое число с плавающей точкой одинарной точности, которое соответствует стандарту IEEE 754
    Double
    64­битовое число с плавающей точкой двойной точности, которое соответствует стандарту IEEE 754
    Boolean true или false
    За исключением типа
    String
    , который находится в пакете java.lang
    , все типы, показанные в данной таблице, входят в пакет scala
    1
    . Например, полное имя типа
    Int обозначается scala.Int
    . Но, учитывая, что все элементы пакета scala и java.lang автоматически импортируются в каждый исходный файл
    Scala, можно повсеместно использовать только простые имена, то есть имена вида
    Boolean
    ,
    Char или
    String
    Опытные Java­разработчики заметят, что основные типы Scala имеют в точно­
    сти такие же диапазоны, как и соответствующие им типы в Java. Это позволяет компилятору Scala в создаваемом им байт­коде преобразовывать экземпляры
    типов значений Scala, например
    Int или
    Double
    , в примитивные типы Java.
    5 .2 . Литералы
    Все основные типы, перечисленные в табл. 5.1, можно записать с помощью
    литералов. Литерал представляет собой способ записи постоянного значения непосредственно в коде.
    1
    Пакеты, кратко рассмотренные в шаге 1 главы 2, более подробно рассматриваются в главе 12.

    106 Глава 5 • Основные типы и операции
    УСКОРЕННЫЙ РЕЖИМ ЧТЕНИЯ ДЛЯ JAVA-ПРОГРАММИСТОВ
    Синтаксис большинства литералов, показанных в данном разделе, совпадает с синтаксисом, применяемым в Java, поэтому знатоки Java могут спокойно пропустить практически весь раздел . Отдельные различия, о которых стоит прочитать, касаются используемых в Scala неформатированных строк (рас- сматриваются в подразделе «Строковые литералы»), а также интерполяции строк . Кроме того, в Scala не поддерживаются восьмеричные литералы, а целочисленные, начинающиеся с нуля, например 031, не проходят ком- пиляцию .
    Целочисленные литералы
    Целочисленные литералы для типов
    Int
    ,
    Long
    ,
    Short и
    Byte используются в двух видах: десятичном и шестнадцатеричном. Способ, применяемый для начала записи целочисленного литерала, показывает основание числа. Если число начинается с
    0x или
    0X
    , то оно шестнадцатеричное (по основанию 16) и может содержать цифры от 0 до 9, а также буквы от A до F в верхнем или нижнем регистре. Вы можете использовать символы подчеркивания (_), чтобы улучшить читаемость больших значений, например:
    val hex = 0x5 // 5: Int val hex2 = 0x00FF // 255: Int val magic = 0xcafebabe // -889275714: Int val billion = 1_000_000_000 // 1000000000: Int
    Обратите внимание на то, что оболочка Scala REPL всегда выводит целочис­
    ленные значения в десятичном виде независимо от формы литерала, которую вы могли задействовать для инициализации этих значений. Таким образом,
    REPL показывает значение переменной hex2
    , которая была инициализиро­
    вана с помощью литерала
    0x00FF
    , как десятичное число
    255
    . (Разумеется, не нужно все принимать на веру. Хорошим способом начать осваивать язык станет практическая работа с этими инструкциями в интерпретаторе по мере чтения данной главы.) Если цифра, с которой начинается число, не ноль и не имеет никаких других знаков отличия, значит, число десятичное (по основанию 10), например:
    val dec1 = 31 // 31: Int val dec2 = 255 // 255: Int val dec3 = 20 // 20: Int
    Если целочисленный литерал заканчивается на
    L
    или l
    , значит, показывает число типа
    Long
    , в противном случае это число относится к типу
    Int
    . По­
    смотрите на примеры целочисленных литералов
    Long
    :

    5 .2 . Литералы 107
    val prog = 0XCAFEBABEL // 3405691582: Long val tower = 35L // 35: Long val of = 31l // 31: Long
    Если
    Int
    ­литерал присваивается переменной типа
    Short или
    Byte
    , то рас­
    сматривается как принадлежащий к типу
    Short или
    Byte
    , если, конечно, его значение находится внутри диапазона, допустимого для данного типа, например:
    val little: Short = 367 // 367: Short val littler: Byte = 38 // 38: Byte
    Литералы чисел с плавающей точкой
    Литералы чисел с плавающей точкой состоят из десятичных цифр, которые также могут содержать необязательный символ десятичной точки, и после них может стоять необязательный символ
    E
    или e
    и экспонента. Посмотрите на примеры литералов чисел с плавающей точкой:
    val big = 1.2345 // 1.2345: Double val bigger = 1.2345e1 // 12.345: Double val biggerStill = 123E45 // 1.23E47: Double val trillion = 1_000_000_000e3 // 1.0E12: Double
    Обратите внимание: экспонента означает степень числа 10, на которую умножается остальная часть числа. Следовательно,
    1.2345e1
    равняется числу 1,2345, умноженному на 10, то есть получается число 12,345. Если литерал числа с плавающей точкой заканчивается на
    F
    или f
    , значит, число относится к типу
    Float
    , в противном случае оно относится к типу
    Double
    Дополнительно литералы чисел с плавающей точкой могут заканчиваться на
    D
    или d
    . Посмотрите на примеры литералов чисел с плавающей точкой:
    val little = 1.2345F // 1.2345: Float val littleBigger = 3e5f // 300000.0: Float
    Последнее значение, выраженное как тип
    Double
    , может также принимать иную форму:
    val anotherDouble = 3e5 // 300000.0: Double val yetAnother = 3e5D // 300000.0: Double
    Большие числовые литералы
    В Scala 3 добавлена экспериментальная функция, которая устраняет огра­
    ничения на размер числовых литералов и позволяет использовать их для

    108 Глава 5 • Основные типы и операции инициализации произвольных типов. Вы можете включить эту функцию с помощью импорта этого языка:
    import scala.language.experimental.genericNumberLiterals
    Вот два примера из стандартной библиотеки:
    val invoice: BigInt = 1_000_000_000_000_000_000_000
    val pi: BigDecimal = 3.1415926535897932384626433833
    Символьные литералы
    Символьные литералы состоят из любого Unicode­символа, заключенного в одинарные кавычки:
    scala> val a = 'A'
    val a: Char = A
    Помимо того что символ представляется в одинарных кавычках в явном виде, его можно указывать с помощью кода из таблицы символов Unicode.
    Для этого нужно записать
    \u
    , после чего указать четыре шестнадцатеричные цифры кода:
    scala> val d = '\u0041'
    val d: Char = A
    scala> val f = '\u0044'
    val f: Char = D
    Такие символы в кодировке Unicode могут появляться в любом месте про­
    граммы на языке Scala. Например, вы можете набрать следующий иденти­
    фикатор:
    scala> val B\u0041\u0044 = 1
    val BAD: Int = 1
    Он рассматривается точно так же, как идентификатор
    BAD
    , являющийся результатом раскрытия символов в кодировке Unicode в показанном ранее коде. По сути, в именовании идентификаторов подобным образом нет ничего хорошего, поскольку их трудно прочесть. Иногда с помощью этого синта­
    ксиса исходные файлы Scala, которые содержат отсутствующие в таблице
    ASCII символы из таблицы Unicode, можно представить в кодировке ASCII.
    И наконец, нужно упомянуть о нескольких символьных литералах, пред­
    ставленных специальными управляющими последовательностями (escape sequences), показанными в табл. 5.2, например:
    scala> val backslash = '\\'
    val backslash: Char = \

    5 .2 . Литералы 109
    Таблица 5.2. Управляющие последовательности специальных символьных литералов
    Литерал
    Предназначение
    \n
    Перевод строки (
    \u000A
    )
    \b
    Возврат на одну позицию (
    \u0008
    )
    \t
    Табуляция (
    \u0009
    )
    \f
    Перевод страницы (
    \u000C
    )
    \r
    Возврат каретки (
    \u000D
    )
    \"
    Двойные кавычки (
    \u0022
    )
    \'
    Одинарная кавычка (
    \u0027
    )
    \\
    Обратный слеш (
    \u005C
    )
    Строковые литералы
    Строковый литерал состоит из символов, заключенных в двойные кавычки:
    scala> val hello = "hello"
    val hello: String = hello
    Синтаксис символов внутри кавычек такой же, как и в символьных литера­
    лах, например:
    scala> val escapes = "\\\"\'"
    val escapes: String = \"'
    Данный синтаксис неудобен для строк, в которых содержится множество управляющих последовательностей, или для строк, не умещающихся в одну строку текста, поэтому для неформатированных строк в Scala включен специальный синтаксис. Неформатированная строка начинается и закан­
    чивается тремя идущими подряд двойными кавычками (
    """
    ). Внутри нее могут содержаться любые символы, включая символы новой строки, кавычки и специальные символы, за исключением, разумеется, трех кавычек подряд.
    Например, следующая программа выводит сообщение, используя неформа­
    тированную строку:
    println("""Welcome to Ultamix 3000.
    Type "HELP" for help.""")
    Но при запуске этого кода получается не совсем то, что хотелось:
    Welcome to Ultamix 3000.
    Type "HELP" for help.

    1   ...   8   9   10   11   12   13   14   15   ...   64


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