#java #kotlin #enums #sealed-class
#java #kotlin #перечисления #запечатанный класс
Вопрос:
Я новичок в Kotlin. Я читаю книгу, и запечатанный класс отображается там как «расширение» Enum. Я не вижу сходства между ними. На мой взгляд, запечатанный класс больше связан с наследованием, потому что каждый класс может наследовать от него и добавлять к нему функции и свойства, например:
sealed class messageType
class MessageSuccess (var msg: String) : MwssageType()
class MessageFailure (var msg: String, var e: Exeception) : MwssageType()
Я не вижу здесь значений, подобных тем, которые мы имеем в Enum, только излом наследования.
Может кто-нибудь объяснить мне, что такое представить между Enum и Sealed, которые я не могу найти?
Может быть, его сила заключается в использовании его с выражением when?
Комментарии:
1. Из документа Закрытые классы в некотором смысле являются расширением классов enum: набор значений для типа enum также ограничен, но каждая константа enum существует только как один экземпляр, тогда как подкласс закрытого класса может иметь несколько экземпляров, которые могут содержать состояние , которое имеет смысл для вас?
2. @Eklavya, я прочитал ссылку, которую вы прикрепили. Я не вижу сходства. Можете ли вы, пожалуйста, помочь мне понять?
3. Закрытый класс, в котором каждый подтип является
object
синглтоном, в точности эквивалентен перечислению.4. @LouisWasserman, но мы можем создать столько экземпляров подтипа, сколько пожелаем. Это цитата из книги, которую я читаю: И в отличие от класса enum, вы можете создавать несколько экземпляров каждого типа.
5. @mohsen, это такой сложный язык. Иногда мне хочется плакать.. Мне не хватает Java
Ответ №1:
Я думаю, что документация подразумевает расширение, на самом деле это не расширение перечислений, а инструмент, подобный перечислениям, с большей мощностью, поскольку он может удерживать состояние. давайте посмотрим на ваш пример с перечислениями.
sealed class SealedMessageType
class MessageSuccess (val msg: String) : SealedMessageType()
class MessageFailure (val e: Exeception) : SealedMessageType()
enum class EnumMessageType {
Success,
Failure
}
и теперь, если вы используете перечисления, у вас есть:
val enumMessageType: EnumMessageType = callNetwork()
when(enumMessageType) {
EnumMessageType.Success -> { TODO() }
EnumMessageType.Failure -> { TODO() }
}
здесь, когда вы используете перечисления, вы не можете получить данные вашего результата из перечислений, и вам нужно получить сообщение или ошибку с какой-либо другой переменной. единственное, что вы можете получить, это тип результата без его состояния. но с запечатанными классами:
val sealedMessageType: SealedMessageType = callNetwork()
when(sealedMessageType) {
is MessageSuccess -> { println(sealedMessageType.msg) }
is MessageFailure -> { throw sealedMessageType.e }
}
среда IDE может интеллектуальным образом преобразовать ваш результат, и вы можете получить состояние вашего результата (в случае успеха — сообщение, в случае сбоя — исключение). это то, что документ подразумевает под расширением.
но в целом вы правы, запечатанный класс связан с наследованием. на самом деле запечатанный класс — это не что иное, как абстрактный класс, который имеет частный конструктор и не может быть создан. давайте посмотрим на декопилированный java-код:
@Metadata(
mv = {1, 4, 0},
bv = {1, 0, 3},
k = 1,
d1 = {"u0000u0014nu0002u0018u0002nu0002u0010u0000nu0000nu0002u0018u0002nu0002u0018u0002nu0000b6u0018u00002u00020u0001Bu0007bu0002¢u0006u0002u0010u0002u0082u0001u0002u0003u0004¨u0006u0005"},
d2 = {"Lcom/example/customview/SealedMessageType;", "", "()V", "Lcom/example/customview/MessageSuccess;", "Lcom/example/customview/MessageFailure;", "app"}
)
public abstract class SealedMessageType {
private SealedMessageType() {
}
// $FF: synthetic method
public SealedMessageType(DefaultConstructorMarker $constructor_marker) {
this();
}
}
@Metadata(
mv = {1, 4, 0},
bv = {1, 0, 3},
k = 1,
d1 = {"u0000u0012nu0002u0018u0002nu0002u0018u0002nu0000nu0002u0010u000enu0002bu0004u0018u00002u00020u0001Bru0012u0006u0010u0002u001au00020u0003¢u0006u0002u0010u0004Ru0011u0010u0002u001au00020u0003¢u0006bnu0000u001au0004bu0005u0010u0006¨u0006u0007"},
d2 = {"Lcom/example/customview/MessageSuccess;", "Lcom/example/customview/SealedMessageType;", "msg", "", "(Ljava/lang/String;)V", "getMsg", "()Ljava/lang/String;", "app"}
)
public final class MessageSuccess extends SealedMessageType {
@NotNull
private final String msg;
@NotNull
public final String getMsg() {
return this.msg;
}
public MessageSuccess(@NotNull String msg) {
Intrinsics.checkNotNullParameter(msg, "msg");
super((DefaultConstructorMarker)null);
this.msg = msg;
}
}
здесь вы можете видеть, что SealedMessageType
на самом деле это абстрактный класс. единственное различие между абстрактным классом и закрытым классом заключается в том, что компилятор генерирует некоторые метаданные для закрытых классов и может предупреждать вас об отсутствующих ветвях при использовании when
ключевого слова, что невозможно сделать с помощью абстрактных классов. в приведенном выше коде вы можете видеть, что SealedMessageType
метаданные класса содержат оба MessageSuccess
и MessageFailure
как дочерние классы, а MessageSuccess
метаданные также содержатся SealedMessageType
как родительские. таких метаданных нет, если вы используете абстрактные классы.
если вы используете этот простой трюк, компилятор выдает вам ошибку, если вы пропустите какие-либо ветви, и IDE может помочь вам реализовать отсутствующие ветви с помощью Alt Enter
. хитрость заключается в определении исчерпывающей функции расширения:
fun main() {
when(callNetwork()) { // error: when' expression must be exhaustive, add necessary 'is MessageSuccess', 'is MessageFailure' branches or 'else' branch instead
}.exhaustive()
}
fun Any?.exhaustive() = this
fun callNetwork(): SealedMessageType {
TODO()
}
Комментарии:
1. @moshen, спасибо за подробное объяснение! Но: 1. Где вы видите метаданные MessageFailute? 2. об исчерпывающем , если «случаи» (не нашли лучшего названия для значений), скажем, выполняют только println(«Что-то»), так какой объект будет запускать исчерпывающий метод?
2. 1. посмотрите на декомпилированный код java, над определением
SealedMessageType
класса вы можете увидеть метаданные 2.exhaustive
метод должен использоваться вwhen
инструкции, как вы можете видеть в примере. это потому, что выдает ошибку, когда вы используетеwhen
ключевое слово в качестве выражения, если вы используете его как оператор, например, мы используем know , он не выдает никаких ошибок. этот трюк заставляетwhen
ключевое слово возвращать что-то, что делает его выражением для получения дополнительной информации обратитесь к kotlinlang.org/docs/reference/control-flow.html3. я так старался понять, что вы написали в комментарии, но безуспешно. Может быть, это потому, что я не могу понять разницу между выражением и оператором, что бы я ни делал. В любом случае спасибо. Я одобряю ваш ответ
4. выражение возвращает некоторое значение, а оператор — нет. например, в java
if
это оператор, но в kotlin он может использоваться либо как оператор, либо как выражение. он может возвращать некоторое значение. ******************val max = if (a > b) a else b
вы можете видеть, чтоif
здесь используется как выражение, потому что оно возвращает значение. то же самое происходит дляwhen
5. @moshen, хорошо, я понимаю, спасибо. Но, допустим, все значения (случаи) в when, все, что они делают, это: println(что-то). Какой объект будет возвращен, поэтому мы можем добавить *.exclusive() . вам нужен любой объект, но единственное, что нужно, это println(что-то). кто будет «запускать» исчерпывающий метод?
Ответ №2:
В терминах функционального программирования запечатанный класс используется для создания категории,