HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-03-10
1 <p>Оператор<em>switch</em>во многих языках воплощает задумку классификации данных на несколько категорий. Но большинство реализаций не могут поддерживать классификацию объектов и поэтому редко используются в программах. В Scala есть очень мощная система сопоставления объекта с образцом. Однако экземпляр далеко не каждого класса можно сопоставлять с образцом без лишних телодвижений. Для упрощения этого процесса были введены два специальных вида сущностей:<strong>кейс-классы (case classes) и кейс-обжэкты (case objects)</strong>. Кейс-классы подобны именованным кортежам, используемым для хранения данных. Часто они могут не иметь своих методов, выступая пассивными контейнерами, ведь Scala предоставляет великое множество других методов взаимодействия. Итак, синтаксис кейс-класса таков:</p>
1 <p>Оператор<em>switch</em>во многих языках воплощает задумку классификации данных на несколько категорий. Но большинство реализаций не могут поддерживать классификацию объектов и поэтому редко используются в программах. В Scala есть очень мощная система сопоставления объекта с образцом. Однако экземпляр далеко не каждого класса можно сопоставлять с образцом без лишних телодвижений. Для упрощения этого процесса были введены два специальных вида сущностей:<strong>кейс-классы (case classes) и кейс-обжэкты (case objects)</strong>. Кейс-классы подобны именованным кортежам, используемым для хранения данных. Часто они могут не иметь своих методов, выступая пассивными контейнерами, ведь Scala предоставляет великое множество других методов взаимодействия. Итак, синтаксис кейс-класса таков:</p>
2 case class CaseClasName(parameterList) { /*опциональное тело класса*/ }<p>Все поля кейс-класса немутирующие и публичные. Это не является нарушением принципа инкапсуляции, поскольку он неприменим к публичным параметрам кейс-класса. Инкапсуляция полей имеет место, когда поля класса должны меняться некоторым сложным взаимосвязанным образом, и поэтому изменение полей должно производиться через методы класса. Здесь же нет никакого изменяемого состояния (мы крайне не рекомендуем его вводить в теле класса, чревато неожиданными последствиями), и инкапсулировать нечего. Инстанцировать экземпляр кейс-класса можно удобным синтаксисом:</p>
2 case class CaseClasName(parameterList) { /*опциональное тело класса*/ }<p>Все поля кейс-класса немутирующие и публичные. Это не является нарушением принципа инкапсуляции, поскольку он неприменим к публичным параметрам кейс-класса. Инкапсуляция полей имеет место, когда поля класса должны меняться некоторым сложным взаимосвязанным образом, и поэтому изменение полей должно производиться через методы класса. Здесь же нет никакого изменяемого состояния (мы крайне не рекомендуем его вводить в теле класса, чревато неожиданными последствиями), и инкапсулировать нечего. Инстанцировать экземпляр кейс-класса можно удобным синтаксисом:</p>
3 CaseClasName(parameter1, parameter2, … , parameterN)<p><strong>Примечание:</strong>здесь "под капотом" находится автоматически сгенерированный метод apply для этого класса, позволяющий пользоваться таким синтаксисом.</p>
3 CaseClasName(parameter1, parameter2, … , parameterN)<p><strong>Примечание:</strong>здесь "под капотом" находится автоматически сгенерированный метод apply для этого класса, позволяющий пользоваться таким синтаксисом.</p>
4 <p>Пример:</p>
4 <p>Пример:</p>
5 case class User(name: Stirng, email: String) val user = User("FooBar", "foo@bar.baz")<p>Второй тип сущностей, case object, похож на case class, но не имеет полей. Зачем же он тогда нужен? Для создания строго типизированных перечислений.</p>
5 case class User(name: Stirng, email: String) val user = User("FooBar", "foo@bar.baz")<p>Второй тип сущностей, case object, похож на case class, но не имеет полей. Зачем же он тогда нужен? Для создания строго типизированных перечислений.</p>
6 <p>Заметим, что кейс-класс не может наследовать от другого кейс-класса, поэтому рекомендуем держать иерархию данных как можно более плоской и как можно менее древоподобной. Всегда можно задать кейс-классам общий надтип и, соответственно, нужный набор полей при помощи трейтов. Например, можно сделать так:</p>
6 <p>Заметим, что кейс-класс не может наследовать от другого кейс-класса, поэтому рекомендуем держать иерархию данных как можно более плоской и как можно менее древоподобной. Всегда можно задать кейс-классам общий надтип и, соответственно, нужный набор полей при помощи трейтов. Например, можно сделать так:</p>
7 trait Account { def username: String def email: String } case class User(username: String, email: String) extends Account case class SuperUser(username: String, email: String, privileges: String) extends Account<p>Каким образом работает сопоставление с шаблоном (pattern matching)? При помощи следующего синтаксиса:</p>
7 trait Account { def username: String def email: String } case class User(username: String, email: String) extends Account case class SuperUser(username: String, email: String, privileges: String) extends Account<p>Каким образом работает сопоставление с шаблоном (pattern matching)? При помощи следующего синтаксиса:</p>
8 value match { case pattern1 if /*опциональное условие 1*/ =&gt; value1 case pattern2 if /*опциональное условие 1*/ =&gt; value2 ... case patternN if /*опциональное условие N*/=&gt; valueN }<p>Шаблоны могут быть очень разнообразными в зависимости от используемого типа данных.</p>
8 value match { case pattern1 if /*опциональное условие 1*/ =&gt; value1 case pattern2 if /*опциональное условие 1*/ =&gt; value2 ... case patternN if /*опциональное условие N*/=&gt; valueN }<p>Шаблоны могут быть очень разнообразными в зависимости от используемого типа данных.</p>
9 <p>Кейс-класс в сопоставлении с шаблоном можно разложить на его параметры при помощи "вызова конструктора" этого кейс-класса. Например, приведенному выше классу<em>User</em>будет соответствовать шаблон<em>User(name, email)</em>. Здесь переменные<em>name</em>и<em>user</em>извлекаются из объекта класса<em>User</em>и после этого становятся доступны блоку<em>value</em>, следующему за<em>=&gt;</em>. Кроме того, вы можете ввести переменную для обозначения объекта, сопоставляемого с образцом, при помощи символа<em>@</em>. Например, такой шаблон введёт переменную<em>user</em>для блока<em>value</em>:</p>
9 <p>Кейс-класс в сопоставлении с шаблоном можно разложить на его параметры при помощи "вызова конструктора" этого кейс-класса. Например, приведенному выше классу<em>User</em>будет соответствовать шаблон<em>User(name, email)</em>. Здесь переменные<em>name</em>и<em>user</em>извлекаются из объекта класса<em>User</em>и после этого становятся доступны блоку<em>value</em>, следующему за<em>=&gt;</em>. Кроме того, вы можете ввести переменную для обозначения объекта, сопоставляемого с образцом, при помощи символа<em>@</em>. Например, такой шаблон введёт переменную<em>user</em>для блока<em>value</em>:</p>
10 <p>Ещё одной полезной особенностью сопоставления с образцом является возможность зафиксировать некоторые поля класса или же, наоборот, игнорировать некоторые из них.</p>
10 <p>Ещё одной полезной особенностью сопоставления с образцом является возможность зафиксировать некоторые поля класса или же, наоборот, игнорировать некоторые из них.</p>
11 <p>Например, мы можем выбирать пользователя только с именем "dave" шаблоном<em>User("dave", email)</em>. Если же нам безразличен email пользователя, мы можем не вводить переменную для этого поля шаблоном User(name, _).</p>
11 <p>Например, мы можем выбирать пользователя только с именем "dave" шаблоном<em>User("dave", email)</em>. Если же нам безразличен email пользователя, мы можем не вводить переменную для этого поля шаблоном User(name, _).</p>
12 <p>Стоит отметить, что сопоставление с образцами происходит в порядке их расположения. Если шаблон<em>User(name, email)</em>находится выше шаблона<em>User("dave", email)</em>, то сопоставления с последним никогда не произойдет, потому что первому из шаблонов соответствует любой экземпляр класса<em>User</em>.</p>
12 <p>Стоит отметить, что сопоставление с образцами происходит в порядке их расположения. Если шаблон<em>User(name, email)</em>находится выше шаблона<em>User("dave", email)</em>, то сопоставления с последним никогда не произойдет, потому что первому из шаблонов соответствует любой экземпляр класса<em>User</em>.</p>
13 <p>Если же вам не нужны поля класса, а нужно просто соответствие по типу, используйте шаблон вида<em>valName: TypeName</em>.</p>
13 <p>Если же вам не нужны поля класса, а нужно просто соответствие по типу, используйте шаблон вида<em>valName: TypeName</em>.</p>
14 <p>Кроме того, существует шаблон "_", которому удовлетворяет любой объект.</p>
14 <p>Кроме того, существует шаблон "_", которому удовлетворяет любой объект.</p>
15 <p>Результат сопоставления с образцом - значение, возвращённое из<em>value</em>, приведенное к общему надтипу всех блоков<em>value1, … valueN</em>.</p>
15 <p>Результат сопоставления с образцом - значение, возвращённое из<em>value</em>, приведенное к общему надтипу всех блоков<em>value1, … valueN</em>.</p>
16 <p>Если ни одному из шаблонов значение не соответствует, выбрасывается исключение<em>PatternMatchingException</em>.</p>
16 <p>Если ни одному из шаблонов значение не соответствует, выбрасывается исключение<em>PatternMatchingException</em>.</p>
17 <p>Приведем пример, объединяющий написанное выше:</p>
17 <p>Приведем пример, объединяющий написанное выше:</p>
18 trait Account case class User(name: Stirng, email: String) extends Account case class SuperUser(name: Stirng, email: String, privileges: String) extends Account case object SomethingStrange extends Account def matcherFunc(acc: Account): Account = acc match { case usr @ User("dave", email) =&gt; println("This is dave, his email:" + email) usr case usr @ User(name, email) if email == "" =&gt; println("User " + name + " has no email" usr case usr @ User(name, email) =&gt; println("User " + name + " has email " + email) usr case su: SuperUser =&gt; println("It`s a superuser") su case smth =&gt; println("We don’t know what is it") } matcherFunc(User("dave", "some@email.com")) matcherFunc(User("dave", "")) matcherFunc(User("notadave", "")) matcherFunc(User("john", "email@any.com")) matcherFunc(SuperUser("neo", "super@secret.email", "every possible")) matcherFunc(SomethingStrange)<p>На самом деле, сопоставление с образцом можно применять не только на кейс-классах. На всех типах-примитивах (числа, строки, символы, …), и типов, для которых определён метод-экстрактор. Например, вы можете сопоставлять строку с регулярным выражением. Просто создайте несколько регулярных выражений и применяйте сопоставление с шаблоном:</p>
18 trait Account case class User(name: Stirng, email: String) extends Account case class SuperUser(name: Stirng, email: String, privileges: String) extends Account case object SomethingStrange extends Account def matcherFunc(acc: Account): Account = acc match { case usr @ User("dave", email) =&gt; println("This is dave, his email:" + email) usr case usr @ User(name, email) if email == "" =&gt; println("User " + name + " has no email" usr case usr @ User(name, email) =&gt; println("User " + name + " has email " + email) usr case su: SuperUser =&gt; println("It`s a superuser") su case smth =&gt; println("We don’t know what is it") } matcherFunc(User("dave", "some@email.com")) matcherFunc(User("dave", "")) matcherFunc(User("notadave", "")) matcherFunc(User("john", "email@any.com")) matcherFunc(SuperUser("neo", "super@secret.email", "every possible")) matcherFunc(SomethingStrange)<p>На самом деле, сопоставление с образцом можно применять не только на кейс-классах. На всех типах-примитивах (числа, строки, символы, …), и типов, для которых определён метод-экстрактор. Например, вы можете сопоставлять строку с регулярным выражением. Просто создайте несколько регулярных выражений и применяйте сопоставление с шаблоном:</p>
19 val regWord = """([\w]+)""".r val regTwoWords = """([\w]+) ([\w]+)""".r def regexMatcher(s: String) = s match { case regWord(word) =&gt; println("Single word: " + word) case regTwoWords(word1, word2) =&gt; println("First word: " + word1 + "second word: " + word2) } regexMatcher("hello") regexMatcher("hello word")<p>Такие действия можно осуществлять, определяя метод<em>unapply</em>и объект-экстрактор. Подробнее об этом можно прочитать в<a>документации</a>. Экстрактор имеет ещё одно полезное применение: с помощью него можно распаковывать кейс-классы и другие классы, имеющие экстрактор, используя точно такой же синтаксис, как вид шаблона в сопоставлении с образцом:</p>
19 val regWord = """([\w]+)""".r val regTwoWords = """([\w]+) ([\w]+)""".r def regexMatcher(s: String) = s match { case regWord(word) =&gt; println("Single word: " + word) case regTwoWords(word1, word2) =&gt; println("First word: " + word1 + "second word: " + word2) } regexMatcher("hello") regexMatcher("hello word")<p>Такие действия можно осуществлять, определяя метод<em>unapply</em>и объект-экстрактор. Подробнее об этом можно прочитать в<a>документации</a>. Экстрактор имеет ещё одно полезное применение: с помощью него можно распаковывать кейс-классы и другие классы, имеющие экстрактор, используя точно такой же синтаксис, как вид шаблона в сопоставлении с образцом:</p>
20 case class User(name: Stirng, email: String) extends Account val user = User("john", "john@doe.com") val User(someName, someEmail) = user<p>Хотя сопоставление с шаблоном имеет больше возможностей, о которых вы можете почитать в документации, здесь мы ограничимся только этими.</p>
20 case class User(name: Stirng, email: String) extends Account val user = User("john", "john@doe.com") val User(someName, someEmail) = user<p>Хотя сопоставление с шаблоном имеет больше возможностей, о которых вы можете почитать в документации, здесь мы ограничимся только этими.</p>
21  
21