Почему Java 8 Option реализован как final, без иерархии Some и None?

В Java Optional реализуется как public final class Optional<T> { ... }, а не как запечатанная иерархия Some и None.

Почему здесь не так? Это обходное решение для отсутствия sealed в Java? Есть ли какие-то более глубокие рассуждения?

Если вы посмотрите на реализацию метода, вы увидите, что, пройдя этот случай, он имеет уродливые нулевые проверки:

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value));
    }
}

Они не только уродливы, но если у вас более длинная цепочка методов, isPresent нужно будет оценивать во время каждого вызова, даже если Optional пуст с самого начала.

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

optional
  .map(i -> i) // isPresent()
  .map(Object::toString) // isPresent()
  .map(String::length) // isPresent()
  .map(...) // isPresent()

Вопрос

Почему не были подтипы, используемые для моделирования пустых и непустых случаев?


Я не спрашиваю конкретно, почему Optional является окончательным, а почему он не был реализован с Some и None, как это делают многие другие языки, поэтому Почему необязательно объявлено как окончательный класс, не очень полезно.

Ответы

Ответ 1

Модификатор final находится в процессе подготовки к большей функции: значения типов, цель для Project Valhalla (функции для Java 10 +).

Вы можете прочитать все о типах значений для Java в статье 2014, приведенной ниже:

Типы значений для Java


Почему нам нужны типы значений в Java?

В Java объекты, созданные из ссылочных типов, имеют identity. Это позволяет ссылаться на конкретные объекты по переменным и сравнивать по ссылке. Все классы /enum/interfaces в настоящее время создают ссылочные типы. Таким образом, все объекты имеют идентификатор.

Но теоретически не все объекты требуют идентификатора, как это явно указано в документе 2014, приведенном выше:

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

Тождества не являются бесплатными, а неизменяемым типам не требуется мутация, которая наследует означает, что они не требуют идентификаторов.

Идентичность неизменяемых типов приводит к чрезмерному охвату:

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

Внедрение типов значений

Джеймс Гослинг написал статью в 1999 году о компиляции неизменяемых типов типов в значения:

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

Эта идея была унаследована экспериментальным проектом Oracle Valhalla, возглавляемым Брайаном Гетцем. В процессе подготовки была создана спецификация для классов, основанных на значении, одно из требований - для класса final.

В документе 2014 по типам значений в Java также раскрывается решение обеспечить соблюдение требования final:

Могут ли значения участвовать в подтипировании на основе наследования?   Нет.

Может ли класс значений быть абстрактным или не окончательным? Нет.


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

Таким образом, мы можем гарантировать, что все методы решаются однозначно в точном типе приемника метода. Вызов метода значения всегда как invokestatic или invokespecial и никогда не нравится invokevirtual или invokeinterface.

Типы значений не могут участвовать в традиционном подтипировании (если это вообще было бы ограничено).

Итак, что это связано с Optional?

Optional - класс, основанный на значении.

В нем упоминалось, что классы, основанные на значении, могут выступать в качестве бокс-версии типов значений:

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

Вложенная форма типа значения должна иметь те же атрибуты, что и типы значений (аналогичные Integer для int), которые включают предотвращение подтипов традиционных классов.

Ответ 2

На самом деле этот вопрос не нов. и он предлагает Natpryce в своей книге: Растущее объектно-ориентированное программное обеспечение. Maybe больше похож на java.util.Optional, но представляет собой 2 состояния по полиморфизму. Действительно, он быстрее, чем Optional, поскольку он применяет State Pattern для перехода состояния present в состояние absent только один раз. что означает, что следующее состояние всегда absent, если текущее состояние absent.

Но я хочу сказать еще один сценарий объектно-ориентированного принципа: Закон Деметры

  • Каждая единица должна иметь только ограниченное знание о других единицах: только единицы " близко", относящиеся к текущему устройству.
  • Каждое устройство должно разговаривать только с друзьями; не разговаривайте с незнакомыми людьми.
  • Общайтесь только со своими ближайшими друзьями.

Как вы можете видеть, LoD-принцип позволит вам написать такой код traveller, который делает код жесткой связью и нарушением инкапсуляции и затрудняет изменение и поддерживать.

В двух словах, если вы согласно принципу LoD, вы не должны писать никаких цепочек вызовов map(one).map(another).map(...) в своей программе. с этой точки зрения, нет никакой пользы для введения иерархии наследования для представления ее внутреннего состояния. Поскольку State Pattern сложнее поддерживать, если вы вводите новое состояние и сложнее для отладки.

Тогда в Optional есть только 2 дополнительных проверки, чем Maybe. одна из них - промежуточная операция map и flatMap, другая - операция терминала orElse, orElseGet и .etc. Поэтому нет большого преимущества применять State Pattern в Optional.