Почему по умолчанию 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
на самом деле более гибко, поскольку позволяет мне решить, хочу ли я его публиковать или нет.