Search
Write a publication
Pull to refresh

Comments 8

Да, Lombok это часто удобно, но реализовать UIState удобнее на рекордах:

public sealed interface UIState permits UIState.Loading, UIState.Error, UIState.Idle, UIState.Success {
    record Loading(boolean isLoading) implements UIState {}
    record Error(String message) implements UIState {}
    record Success(Object data) implements UIState {}
    enum Idle implements UIState {INSTANCE}
}

и вот пример работы с ним:

public void processUIState(UIState uiState) {
    switch (uiState) {
        case UIState.Loading loading -> System.out.println("loading: " + loading.isLoading());
        case UIState.Error error -> System.out.println("error: " + error.message());
        case UIState.Success success -> System.out.println("success: " + success.data());
        case UIState.Idle __ -> System.out.println("idle");
        default -> throw new IllegalStateException("Unexpected value: " + uiState);
    }
}

Даже с типизированным Maybe фактически нет проблем, причём за счёт использования enum создать ещё один инстанс Nothing даже через рефлексию будет проблематично.

public sealed interface Maybe<T> permits Maybe.Just, Maybe.Nothing {

    @Nonnull
    @SuppressWarnings("unchecked")
    static <T> Maybe<T> maybe(@Nullable T value) {
        return value == null ? (Maybe<T>) Nothing.INSTANCE : new Just<>(value);
    }

    record Just<T>(T value) implements Maybe<T> {}
    enum Nothing implements Maybe<Object> {INSTANCE}

}

Я может что-то пропустил, но вроде как при полном перечислении наследников sealed класса в switch, не нужно default указывать?

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

Интересно что структура sealed классов не обязана быть полностью закрытой так как один из разрешённых вариантов может иметь модификатор non-sealed и от него можно будет наследоваться.
Может быть полезным для библиотек и прочих мест где нужно всё же дать возможность неконтролируемо расширять иерархию классов.

public sealed interface Demo permits Demo.Sealed1, Demo.Sealed2, Demo.Open {
    // Эти классы входят в закрытую часть структуры
    final class Sealed1 implements Demo {}
    final class Sealed2 implements Demo {}
    non-sealed class Open implements Demo {}

    // Это пример структуры, которая уже не контролируется ограничениями по `sealed`
    class External extends Open {}
}

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

Я так понимаю что наличие информации о всех наследниках в родителе это не побочная проблема, а одна из целей функционала.
А ещё такая зависимость есть в enum-ах, причём даже более жёсткая: родительский класс не только знает все его реализации, но они ещё и объявлены должны быть строго в пределах родительского класса.
Тогда как наследники sealed-класса могут быть объявлены где угодно (но в пределах видимости родительского - так как в нём необходимо указать потомка). Ну и с учётом возможности дать возможность расширять иерархию классов через non-sealed потомка это вообще не выглядит не преодолимой проблемой.
Другое дело что не все классы имеет смысл объявлять sealed.

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

Sign up to leave a comment.