Sealed class kotlin что это

Изолированные классы

Изолированные классы и интерфейсы позволяют выразить ограниченные иерархии классов, которые обеспечивают больший контроль над наследованием. Во время компиляции известны все прямые наследники изолированного класса. Никакие другие наследники не могут появиться после компиляции модуля с изолированным классом. Например, сторонние клиенты не могут расширить ваш изолированный класс в своем коде. Таким образом, каждый экземпляр изолированного класса имеет тип из ограниченного набора, который известен при компиляции этого класса.

То же самое справедливо для изолированных интерфейсов и их реализаций: новые реализации не могут появиться после компиляции модуля с изолированным интерфейсом.

Изолированные классы похожи на enum-классы: набор значений enum типа также ограничен, но каждая enum-константа существует только в единственном экземпляре, в то время как наследник изолированного класса может иметь несколько экземпляров, которые могут нести в себе какое-то состояние.

В качестве примера рассмотрим API библиотеки. Вероятно, он будет содержать классы ошибок, чтобы пользователи библиотеки могли обрабатывать возникающие ошибки. Если иерархия таких классов ошибок включает интерфейсы или абстрактные классы, видимые в общедоступном API, то ничто не препятствует их реализации или расширению в клиентском коде. Однако библиотека не знает об ошибках, объявленных за её пределами, поэтому не может обрабатывать их согласованно с помощью собственных классов. Благодаря изолированной иерархии классов ошибок авторы библиотек могут быть уверены, что им известны все возможные типы ошибок, и никакие другие не могут появиться позже.

Чтобы описать изолированный класс или интерфейс, укажите модификатор sealed перед его именем.

Сам по себе изолированный класс является абстрактным, он не может быть создан напрямую и может иметь абстрактные компоненты.

Конструкторы изолированных классов могут иметь одну из двух видимостей: protected (по умолчанию) или private :

Расположение прямых наследников

Прямые наследники изолированных классов и интерфейсов должны быть объявлены в том же пакете. Они могут быть верхнего уровня или вложены в любое количество других проименованных классов, проименованных интерфейсов или проименованных объектов. Наследники могут иметь любую видимость, если они совместимы с обычными правилами наследования в Kotlin.

Наследники изолированных классов должны иметь правильные имена. Они не могут быть локальными или анонимными объектами.

Enum-классы не могут расширять изолированный класс (как и любой другой класс), но они могут реализовывать изолированные интерфейсы.

Эти ограничения не применяются к непрямым наследникам. Если прямой наследник изолированного класса не помечен как изолированный, он может быть расширен любыми способами, разрешенными его модификаторами:

Наследование в мультиплатформенных проектах

Если изолированный класс объявлен как expected в общем модуле и имеет actual реализации в платформенном модуле, как ожидаемая, так и актуальные версии могут иметь наследников в своих модулях.

Изолированные классы и выражение when

Источник

Запечатанные классы (sealed)

Добавление модификатора sealed к суперклассу ограничивает возможность создания подклассов. Все прямые подклассы должны быть вложены в суперкласс. Запечатанный класс не может иметь наследников, объявленных вне класса.

В методе eval() при использовании when не пришлось использовать ветку else, так как sealed позволяет указать все доступные варианты и значение по умолчанию не требуется.

Если позже вы добавите новый подкласс, то выражение when будет ругаться и вы быстро вспомните, что нужно добавить новый код в программу.

По умолчанию запечатанный класс открыт и модификатор open не требуется. Запечатанные классы немного напоминают enum.

Пример: Продам кота дёшево

Sealed class kotlin что это. sealed. Sealed class kotlin что это фото. Sealed class kotlin что это-sealed. картинка Sealed class kotlin что это. картинка sealed

Создадим запечатанный класс AcceptedCurrency и три подкласса на его основе. Обратите внимание, что сейчас Kotlin разрешает объявлять подклассы не внутри запечатанного класса, а на одном уровне (для сравнения смотри старые примеры выше).

В классе активности создадим список принимаемых валют для покупки котят и применим его к адаптеру выпадающего списка.

Если запустить пример прямо сейчас, то в выпадающем списке увидим служебную информацию о классах. Не совсем то, что мы хотели увидеть.

Sealed class kotlin что это. sealed1. Sealed class kotlin что это фото. Sealed class kotlin что это-sealed1. картинка Sealed class kotlin что это. картинка sealed1

Внесём изменения в запечатанный класс, чтобы у него появилось новое свойство.

Если вы пропустите какой-то подкласс в выражении when, то компилятор будет ругаться. Это удобно, когда вы будете вносить изменения в код.

Поменяем код для адаптера.

Теперь названия выводятся нормально.

Sealed class kotlin что это. sealed2. Sealed class kotlin что это фото. Sealed class kotlin что это-sealed2. картинка Sealed class kotlin что это. картинка sealed2

Установим зависимость валют от рубля. Создадим в запечатанном классе абстрактное свойство valueInRubels. После этого студия потребует дополнить код у всех подклассов.

Добавим в класс ещё одну переменную ammount и функцию для подсчёта общей суммы.

Напишем код для щелчка кнопки. Вам нужно ввести минимальную и максимальную цену в любой валюте для одного котёнка, а кнопка покажет цену в рублях. Если вы увидите, что покупатель из Америки, то выставляете ценник в долларах. Если покупатель из непонятной страны, то ставьте тугрики (какая вам разница?).

В примере мы выставили цену от 2 до 3 долларов за котёнка (что-то мы продешевили) и сразу видим, сколько заработаем в рублях.

Источник

Sealed classes

Sealed classes and interfaces represent restricted class hierarchies that provide more control over inheritance. All direct subclasses of a sealed class are known at compile time. No other subclasses may appear after a module with the sealed class is compiled. For example, third-party clients can’t extend your sealed class in their code. Thus, each instance of a sealed class has a type from a limited set that is known when this class is compiled.

The same works for sealed interfaces and their implementations: once a module with a sealed interface is compiled, no new implementations can appear.

In some sense, sealed classes are similar to enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances, each with its own state.

As an example, consider a library’s API. It’s likely to contain error classes to let the library users handle errors that it can throw. If the hierarchy of such error classes includes interfaces or abstract classes visible in the public API, then nothing prevents implementing or extending them in the client code. However, the library doesn’t know about errors declared outside it, so it can’t treat them consistently with its own classes. With a sealed hierarchy of error classes, library authors can be sure that they know all possible error types and no other ones can appear later.

To declare a sealed class or interface, put the sealed modifier before its name:

A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members.

Constructors of sealed classes can have one of two visibilities: protected (by default) or private :

Location of direct subclasses

Direct subclasses of sealed classes and interfaces must be declared in the same package. They may be top-level or nested inside any number of other named classes, named interfaces, or named objects. Subclasses can have any visibility as long as they are compatible with normal inheritance rules in Kotlin.

Subclasses of sealed classes must have a proper qualified name. They can’t be local nor anonymous objects.

enum classes can’t extend a sealed class (as well as any other class), but they can implement sealed interfaces.

These restrictions don’t apply to indirect subclasses. If a direct subclass of a sealed class is not marked as sealed, it can be extended in any ways that its modifiers allow:

Inheritance in multiplatform projects

There is one more inheritance restriction in multiplatform projects: direct subclasses of sealed classes must reside in the same source set. It applies to sealed classes without the expect and actual modifiers.

If a sealed class is declared as expect in a common source set and have actual implementations in platform source sets, both expect and actual versions can have subclasses in their source sets. Moreover, if you use a hierarchical structure, you can create subclasses in any source set between the expect and actual declarations.

Sealed classes and when expression

The key benefit of using sealed classes comes into play when you use them in a when expression. If it’s possible to verify that the statement covers all cases, you don’t need to add an else clause to the statement. However, this works only if you use when as an expression (using the result) and not as a statement:

when expressions on expect sealed classes in the common code of multiplatform projects still require an else branch. This happens because subclasses of actual platform implementations aren’t known in the common code.

Источник

Sealed типы в Java

Язык Java с недавнего времени стал активно развиваться. Шестимесячный релиз версий Java не может не радовать Java разработчика новыми фичами.

Одним из приоритетных направлений развития Java является сопоставление с образцом (pattern matching). Pattern matching раскрывают перед разработчиком возможность писать код более гибко и красивее, при этом оставляя его понятным.

Ключевыми блоками для pattern matching в Java планируется записи (record) и запечатаные (sealed) типы.

Записи (record) предоставляют лаконичный синтаксис для объявления классов, которые являются простыми носителями постоянных, неизменяемых наборов данных.

Появятся в Java 14 в виде preview feature.

Запечатанные (sealed) типы — это классы или интерфейсы, которые накладывают ограничения на другие классы или интерфейсы, которые могут расширять или реализовывать их. С большой долей вероятности могут появится в Java 15.

Представляют с себя enum типы на стероидах.

Для объявления sealed класса или интерфейса используется sealed модификатор. Список подтипов может быть перечислено во времени объявления sealed класса или интерфейса
после ключевого слова permits. В случае если подтипы находятся в том же пакете или модуле, то компилятор сам может вывести список подтипов и permits в объявлении sealed
класса или интерфейса можно опустить.

Если подтип является абстрактным, то он неявно становится помеченный sealed модификатором, если его явно не пометить модификатором non-sealed.

Как видно Java вводит новый тип ключевых слов которые называются hyphenated keywords.
Такие ключевые слова буду состоять из двух слов разделенных черточкой.

Конкретные подтипы неявно становится final, если его явно не пометить модификатором non-sealed. Хотя мне кажется, было бы лучше заиспользовать non-final, если не хочется его делать конечным.

Для того, чтобы осуществить поддержку sealed типов в class-файлы добавляется новый атрибут PermittedSubtypes, который сохраняет список подтипов.

Также для того чтобы можно работать с sealed типами через рефлексию в java.lang.Class добавляются два метода.

Первый метод возвращает массив объектов java.lang.constant.ClassDesc, которые представляют список подтипов, если класс помеченный sealed модификатор. Если класс не помеченный sealed модификатор, возвращается пустой массив. Второй метод возвращает true, если данный класс или интерфейс помеченный sealed модификатор.

Все это позволяет при переборе списка подтипов в switch-выражении компилятору определить, перебраны все подтипы или нет.

Ветку default необязательно указывать, ведь компилятор определяет все допустимые подтипы.
И если добавляется новый подтип, то компилятор определит, что в switch не все подтипы рассмотрены, и выкинет ошибку во времени компиляции.

Идея sealed типов не новая. Например, в языке Kotlin существуют sealed классы. Но sealed интерфейсов нету.

sealed классы в языке Kotlin неявно являются абстрактными и имеют приватный конструктор по умолчанию. Соответственно, подтипы должны быть вложенными в класс. На языке Java можно представить следующим образом.

В этом случае можно гарантировать, что все подтипы можно определить во времени компиляции.

Это все хорошо, но что если хочется попробовать sealed классы, а они еще не вышли. Благо ничего не мешает реализовать простенький визитер, подобно тому как работает when-выражение в Kotlin.

Реализация визитера очень проста. Берем тип целевого объекта и в в зависимости от типа ветки, выполняем лямбда-выражения.

Но кроме того, нам нужно знать подтипы целевого объекта и сравнить с типами указанных в ветках. И в случае если какой-то подтип не проверили бросим исключение.

Соответственно, чтобы каждый раз не вычислять подтипы класса, можем кешировать. При этом нам нужно проверить, что целевой класс абстрактный, имеет приватный конструктор, и все вложенные классы унаследованные от целевого класса.

Напишем простой бенчмарк и померяем в среднем на сколько отличаются скорость выполнения визитера и кода написаного на чистой Java. Полный исходный бенчмарка можно посмотреть здесь.

Подведя итог, можно сказать sealed типы очень хорошая фича, использования которой упростить написания кода и сделает код более надежнее.

Полный исходной код визитора можно посмотреть на github.

Источник

What are sealed classes in Kotlin?

I’m a beginner in Kotlin and recently read about Sealed Classes. But from the doc the only think I actually get is that they are exist.

The doc stated, that they are «representing restricted class hierarchies». Besides that I found a statement that they are enums with superpower. Both aspects are actually not clear.

So can you help me with the following questions:

UPDATE: I carefully checked this blog post and still can’t wrap my head around that concept. As stated in the post

The feature allows us to define class hierarchies that are restricted in their types, i.e. subclasses. Since all subclasses need to be defined inside the file of the sealed class, there’s no chance of unknown subclasses which the compiler doesn’t know about.

Why the compiler doesn’t know about other subclasses defined in other files? Even IDE knows that. Just press Ctrl+Alt+B in IDEA on, for instance, List<> definition and all implementations will be shown even in other source files. If a subclass can be defined in some third-party framework, which not used in the application, why should we care about that?

Sealed class kotlin что это. mtlIQ. Sealed class kotlin что это фото. Sealed class kotlin что это-mtlIQ. картинка Sealed class kotlin что это. картинка mtlIQ

3 Answers 3

Say you have a domain (your pets) where you know there is a definite enumeration (count) of types. For example, you have two and only two pets (which you will model with a class called MyPet ). Meowsi is your cat and Fido is your dog.

Compare the following two implementations of that contrived example:

Because you have used sealed classes, when you need to perform an action depending on the type of pet, then the possibilities of MyPet are exhausted in two and you can ascertain that the MyPet instance will be exactly one of the two options:

If you don’t use sealed classes, the possibilities are not exhausted in two and you need to include an else statement:

In other words, without sealed classes there is not exhaustion (complete coverage) of possibility.

Note that you could achieve exhaustion of possiblity with Java enum however these are not fully-fledged classes. For example, enum cannot be subclasses of another class, only implement an interface (thanks EpicPandaForce).

What is the use case for complete exhaustion of possibilities? To give an analogy, imagine you are on a tight budget and your feed is very precious and you want to ensure you don’t end up feeding extra pets that are not part of your household.

Without the sealed class, someone else in your home/application could define a new MyPet :

And this unwanted pet would be fed by your feed method as it is included in the else statement:

Likewise, in your program exhaustion of possibility is desirable because it reduces the number of states your application can be in and reduces the possibility of bugs occurring where you have a possible state where behaviour is poorly defined.

Hence the need for sealed classes.

Mandatory else

Note that you only get the mandatory else statement if when is used as an expression. As per the docs:

If [ when ] is used as an expression, the value of the satisfied branch becomes the value of the overall expression [. and] the else branch is mandatory, unless the compiler can prove that all possible cases are covered with branch conditions

This means you won’t get the benefit of sealed classes for something like this):

To get exhaustion for this scenario, you would need to turn the statement into an expression with return type.

Some have suggested an extension function like this would help:

Others have suggested that an extension function pollutes the namespace and other workarounds (like compiler plugins) are required.

See here for more about this problem.

Sealed class kotlin что это. . Sealed class kotlin что это фото. Sealed class kotlin что это-. картинка Sealed class kotlin что это. картинка

If you’ve ever used an enum with an abstract method just so that you could do something like this:

When in reality what you wanted is this:

And you only made it an enum abstract method to receive compile time guarantee that you always handle this assignment in case a new enum type is added; then you’ll love sealed classes because sealed classes used in assignments like that when statement receive a «when should be exhaustive» compilation error which forces you to handle all cases instead of accidentally only some of them.

So now you cannot end up with something like:

Also, enums cannot extend classes, only interfaces; while sealed classes can inherit fields from a base class. You can also create multiple instances of them (and you can technically use object if you need the subclass of the sealed class to be a singleton).

Sealed class kotlin что это. kLhXq. Sealed class kotlin что это фото. Sealed class kotlin что это-kLhXq. картинка Sealed class kotlin что это. картинка kLhXq

Sealed classes are easier to understand when you understand the kinds of problems they aim to solve. First I’ll explain the problems, then I’ll introduce the class hierarchies and the restricted class hierarchies step by step.

Problems

Tagged class

The following function checks the state of the currently passed object with the help of enums and displays the respective status:

As you can see, we are able to display the different states properly. We also get to use exhaustive when expression, thanks to enums. But there are various problems with this pattern:

Multiple responsibilities

The class DeliveryStatus has multiple responsibilities of representing different states. So it can grow bigger, if we add more functions and properties for different states.

More properties than needed

An object has more properties than it actually needs in a particular state. For example, in the function above, we don’t need any property for representing the Preparing state. The trackingId property is used only for the Dispatched state and the receiversName property is concerned only with the Delivered state. The same is true for functions. I haven’t shown functions associated with states to keep the example small.

No guarantee of consistency

Since these unused properties can be set from unrelated states, it’s hard to guarantee the consistency of a particular state. For example, one can set the receiversName property on the Preparing state. In that case, the Preparing will be an illegal state, because we can’t have a receiver’s name for the shipment that hasn’t been delivered yet.

Need to handle null values

Introducing Class Hierarchies

Unrestricted class hierarchy: abstract class

Instead of managing all the states in a single class, we separate the states in different classes. We create a class hierarchy from an abstract class so that we can use polymorphism in our displayStatus() function:

The trackingId is now only associated with the Dispatched state and receiversName is only associated with the Delivered state. This solves the problems of multiple responsibilities, unused properties, lack of state consistency and null values.

Our displayStatus() function now looks like the following:

So we solved all the problems mentioned in the tagged class section by introducing a class hierarchy. But the unrestricted class hierarchies have the following shortcomings:

Unrestricted Polymorphism

Need for the else branch

Due to unrestricted polymorphism, we need an else branch to decide what to do when an unexpected state is passed. If we use some default state instead of throwing an exception and then forget to take care of any newly added subclass, then that default state will be displayed instead of the state of the newly created subclass.

No exhaustive when expression

Sealed Classes to the Rescue

Restricted class hierarchy: sealed class

Using the sealed modifier on a class does two things:

Our displayStatus() function now looks cleaner:

Sealed classes offer the following advantages:

Restricted Polymorphism

No need for the else branch

Due to restricted polymorphism, we don’t need to worry about other possible subclasses of DeliveryStatus and throw an exception when our function receives an unexpected type. As a result, we don’t need an else branch in the when expression.

Exhaustive when expression

Just like all the possible values of an enum class are contained inside the same class, all the possible subtypes of a sealed class are contained inside the same package and the same compilation unit. So, the compiler knows all the possible subclasses of this sealed class. This helps the compiler to make sure that we have covered(exhausted) all the possible subtypes in the when expression. And when we add a new subclass and forget to cover it in the when expression, it flags an error at compile time.

Note that in the latest Kotlin versions, your when is exhaustive for the when expressions as well the when statements.

Why in the same file?

The same file restriction has been removed since Kotlin 1.5. Now you can define the subclasses of the sealed class in different files but the files need to be in the same package and the same compilation unit. Before 1.5, the reason that all the subclasses of a sealed class needed to be in the same file was that it had to be compiled together with all of its subclasses for it to have a closed set of types. If the subclasses were allowed in other files, the build tools like Gradle would have to keep track of the relations of files and this would affect the performance of incremental compilation.

IDE feature: Add remaining branches

In our small example there are just three branches but in a real project you could have hundreds of branches. So you save the effort of manually looking up which subclasses you have defined in different files and writing them in the when expression one by one in another file. Just use this IDE feature. Only the sealed modifier enables this.

That’s it! Hope this helps you understand the essence of sealed classes.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *