В чем разница между запечатанным классом и принципом наследования в Kotlin?

Я новичок в Котлине. Я читаю книгу, и там отображается запечатанный класс как расширение Enum. Я не вижу между ними сходства. На мой взгляд, класс Sealed больше связан с наследованием, потому что каждый класс может наследовать от него и добавлять к нему функции и свойства. Например:

sealed class messageType
class MessageSuccess (var msg: String) : MwssageType()
class MessageFailure (var msg: String, var e: Exeception) : MwssageType()

Я не вижу здесь значений, как в Enum, только изгиб наследования. Может ли кто-нибудь объяснить мне, что я не могу найти между Enum и Sealed? Может быть, его сила в том, чтобы использовать его с выражением когда?


person Eitanos30    schedule 24.10.2020    source источник
comment
Из документа Запечатанные классы в некотором смысле являются расширением классов перечисления: набор значений для типа перечисления также ограничен, но каждая константа перечисления существует только как единственный экземпляр, тогда как подкласс класса Запечатанный класс может иметь несколько экземпляров, которые могут содержать состояние, которое имеет для вас смысл?   -  person Eklavya    schedule 24.10.2020
comment
@Eklavya, я прочитал ссылку, которую вы прикрепили. Я не вижу сходства. Не могли бы вы помочь мне понять?   -  person Eitanos30    schedule 24.10.2020
comment
Запечатанный класс, в котором каждый подтип является object - одноэлементным, - в точности эквивалентен перечислению.   -  person Louis Wasserman    schedule 24.10.2020
comment
@LouisWasserman, но мы можем создать столько экземпляров подтипа, сколько захотим. Это цитата из книги, которую я читаю: И, в отличие от класса enum, вы можете создать несколько экземпляров каждого типа.   -  person Eitanos30    schedule 24.10.2020
comment
@ Eitanos30: вот почему я явно сказал object, это класс, который может иметь только один экземпляр.   -  person Louis Wasserman    schedule 24.10.2020
comment
@LouisWasserman, 1. Можно ли сделать подтип объектом? 2. Подтип не имеет значений, но Enum имеет 3. Подтип может добавлять свойства и методы.   -  person Eitanos30    schedule 24.10.2020
comment
@ Eitanos30 да, подтипы могут быть объектами.   -  person mohsen    schedule 24.10.2020
comment
@mohsen, это такой трудный язык. Иногда хочется плакать .. Я скучаю по Java   -  person Eitanos30    schedule 24.10.2020
comment
@ Eitanos30 нет, на самом деле нет, у него много функций, которые иногда усложняют его: D   -  person mohsen    schedule 24.10.2020


Ответы (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 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\b6\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002\u0082\u0001\u0002\u0003\u0004¨\u0006\u0005"},
   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 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0004\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006¨\u0006\u0007"},
   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()
}
person mohsen    schedule 24.10.2020
comment
@moshen, Спасибо за подробное объяснение! Но: 1. Где вы видите метаданные MessageFailute? 2. насчет исчерпывающего, если кейсы (не нашли лучшего названия для значений), скажем, выполняются только println (что-то), то какой объект будет запускать исчерпывающий метод? - person Eitanos30; 24.10.2020
comment
1. Посмотрите на декомпилированный код java, над определением класса SealedMessageType вы можете увидеть метаданные 2. Метод exhaustive должен использоваться в операторе when, как вы можете видеть в примере. это потому, что выдает ошибку, когда вы используете ключевое слово when в качестве выражения, если вы используете его как оператор, как мы используем know, он не дает никаких ошибок. этот трюк заставляет ключевое слово when возвращать что-то, что делает его выражением, для получения дополнительной информации см. kotlinlang. org / docs / reference / control-flow.html - person mohsen; 24.10.2020
comment
Я так старался понять, что вы написали в комментарии, но безуспешно. Может быть, это потому, что я не могу понять разницу между выражением и утверждением, что бы я ни делал. Спасибо, в любом случае. Я одобряю твой ответ - person Eitanos30; 24.10.2020
comment
выражение возвращает какое-то значение, а инструкция - нет. например, в java if - это оператор, но в kotlin он может использоваться как оператор или выражение. он может возвращать какое-то значение. ****************** val max = if (a > b) a else b вы можете видеть, что if используется здесь как выражение, потому что оно возвращает значение. то же самое происходит с when - person mohsen; 24.10.2020
comment
@moshen, хорошо, я понимаю, спасибо. Но допустим, что все значения (случаи) в when, все, что они делают, это: println (something). Какой объект будет возвращен, поэтому мы можем добавить * .exhaustive (). вам нужен любой объект, но единственное - println (что-то). кто будет использовать исчерпывающий метод? - person Eitanos30; 24.10.2020
comment
в этом случае исчерпывающий метод выполняется для объекта Unit. потому что println() возвращает Unit (unit похож на void в java, но здесь это реальный объект) - person mohsen; 25.10.2020
comment
@ moshen, спасибо, я тоже проверил :) Большое спасибо !!!! - person Eitanos30; 25.10.2020
comment
@moshen, не могли бы вы уточнить свой ответ с объяснением в своем ответе, что вы имеете в виду, когда говорите: здесь, когда вы используете перечисления, вы не можете получить данные своего результата из перечислений, и вам нужно получить сообщение или ошибка с другой переменной. - person Eitanos30; 25.10.2020