Почему по умолчанию clone() в Cloneable в Java 8

Cloneable в Java по своей сути нарушена. В частности, моя самая большая проблема с интерфейсом заключается в том, что он ожидает поведения метода, который не определяет сам метод. Поэтому, если вы переходите через список Cloneable, вы должны использовать отражение для доступа к его определенному поведению. Однако в Java 8 у нас теперь есть методы по умолчанию, и теперь я спрашиваю, почему в Cloneable нет метода clone() по умолчанию.

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

Я представляю себе невнимательность Object.clone() и меняю свой внутренний код на что-то вроде:

if(this instanceof Cloneable) {
    return ((Cloneable) this).clone();
}
else {
    throw new CloneNotSupportedException();
}

И перемещение по любой магии делает clone() делать свое дело как метод по умолчанию в Cloneable. Это на самом деле не означает, что clone() может быть легко реализовано неправильно, но это другое обсуждение само по себе.

Насколько я могу все еще это изменение будет полностью обратно совместимо:

  • Классы, которые в настоящее время переопределяют clone(), но не реализуют Cloneable (ПОЧЕМУ?!), будут по-прежнему технически хорошими (даже если это невозможно, но это ничем не отличается от предыдущего).
  • Классы, которые в настоящее время переопределяют clone(), но реализация Cloneable все равно будет работать одинаково при ее реализации.
  • Классы, которые в настоящее время не переопределяют clone(), но реализовало Cloneable (ПОЧЕМУ?!) теперь следовать спецификации, даже если это не полностью функционально правильно.
  • Те, которые использовали отражение и ссылались на Object.clone(), по-прежнему функционально работали.
  • super.clone() будет по-прежнему функционально одинаковым, даже если он ссылается на Object.clone().

Не говоря уже о том, что это решит огромную проблему, которая есть Cloneable. Несмотря на то, что он утомительно и по-прежнему легко реализуется некорректно, он будет решать огромную объектно-ориентированную проблему с интерфейсом.

Единственная проблема, с которой я вижу, - это те, которые реализуют Cloneable не обязаны переопределять clone(), но это ничем не отличается от того, что было раньше.

Было ли это обсуждено внутренне, но так и не пришло в себя? Если да, то почему? Если по той причине, что интерфейсы не могут использовать методы Object по умолчанию, не имеет смысла делать исключение в этом случае, поскольку все объекты, наследующие Cloneable, ожидали clone() в любом случае?

Ответ 1

Ваш вопрос несколько широк и более дискуссионный, но я могу пролить свет на этот вопрос.

В Эффективной Java ™ Джошуа Блох дает довольно краткое изложение ситуации. Он открывает немного истории за Cloneable

Интерфейс Cloneable был предназначен как интерфейс mixin для объектов рекламируйте, что они допускают клонирование. К сожалению, он не может служить этой цели. Его основной недостаток заключается в том, что он не имеет метода клонирования, а метод клонирования объектов защищен. Вы не можете, не прибегая к отражению, вызывать метод clone для объекта только потому, что он реализует Cloneable.

и продолжает рассуждения

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

и

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

В этом есть много деталей, но нужно отметить только одну проблему:

Архитектура клона несовместима с нормальным использованием конечных полей, относящихся к изменяемым объектам.

Я думаю, этого достаточно, чтобы рассуждать о том, что метод default в интерфейсе делает клонирование. Было бы чрезвычайно сложно реализовать его правильно.

Ответ 2

Мой опыт, вероятно, далеко не основной, но я использую clone() и поддерживаю текущий дизайн Cloneable. Вероятно, было бы лучше иметь его вместо аннотации, но Cloneable появился задолго до аннотаций. Мое мнение таково, что Cloneable - вещь низкого уровня, и никто не должен делать что-то вроде obj instanceof Cloneable. Если вы используете Cloneable в некоторой бизнес-логике, гораздо лучше объявить свой собственный интерфейс или абстрактный класс, который предоставляет clone() публике и реализует его во всех ваших бизнес-логических объектах. Иногда вы, вероятно, захотите не раскрывать clone() на самом деле, но создайте свой собственный метод, который использует clone() внутренне.

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

public abstract class NamedObject implements Cloneable {
    private String name;

    protected NamedObject(String name) {
        this.name = name;
    }

    public final String getName() {
        return name;
    }

    public NamedObject clone(String newName) {
        try {
            NamedObject clone = (NamedObject)super.clone();
            clone.name = newName;
            return clone;
        }
        catch(CloneNotSupportedException ex) {
            throw new AssertionError();
        }
    }
}

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

Другим случаем, когда я использую Cloneable, является реализация Spliterator.trySplit(). См. реализация простого разделителя, который возвращает заданное количество постоянных объектов. Он имеет четыре специализации (для объектов, ints, longs и double), но благодаря clone() я могу реализовать trySplit() только один раз в суперклассе. Опять же, я не хочу раскрывать clone(), я просто хочу использовать его сам.

Таким образом, отсутствие метода clone() в интерфейсе Cloneable на самом деле более гибко, поскольку позволяет мне решить, хочу ли я его публиковать или нет.