Почему не рекомендуется использовать Cloneable?

140

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

Поэтому у меня вопрос: почему он еще не устарел? Если основная группа разработчиков Java решила, что это не работает, они, должно быть, также рассмотрели вопрос об отказе от поддержки. Каковы их причины против этого (в Java 8 это все еще не устарело )?

Као
источник
41
Этот вопрос не «в первую очередь основан на мнении», как многие, по-видимому, считают вправе судить. Те, у кого нет ничего, кроме мнения о причинах, просто не могут ответить. Однако это правда, что у вас есть лишь небольшой шанс получить здесь авторитетный ответ. Верно также и то, что ваш вопрос не касается вашей решаемой проблемы, так что это, по крайней мере, не по теме.
Марко Топольник
6
@MarkoTopolnik Я согласен с тем, что в мире есть люди, которые могут дать авторитетный ответ, но я не верю, что это тест, который мы применяем здесь. Причина закрытия гласит, что «ответы на этот вопрос будут почти полностью основываться на мнениях». Я подозреваю, что здесь так и будет, если нам не повезет.
Дункан Джонс
2
Вот «Как и когда» отказаться от Oracle ... ( docs.oracle.com/javase/6/docs/technotes/guides/javadoc/… ) Клонируемый интерфейс может оказаться "ошибочным или очень неэффективным", но он очень открыт для мнений.
Maxx
8
@Duncan Я все еще не считаю справедливым выносить суждение по вопросу, основываясь на моих предположениях об отсутствии дисциплины со стороны респондентов . Если пользователь не знает причину, о которой его спрашивают, он не имеет права злоупотреблять функцией автоответчика, чтобы изложить свое мнение по этому вопросу.
Марко Топольник
4
@lexicore Да, именно так - и вы можете поспорить, что они тщательно рассмотрели этот вариант и, следовательно, должны иметь веские причины не осуждать его. Их собственная критика Cloneableшироко известна.
Марко Топольник

Ответы:

120

Существует ошибка , представленная в 1997 году в базу данных ошибок Java о добавлении clone()метод Cloneable, так что он больше не будет бесполезно. Он был закрыт постановлением "исправлять не буду" и обоснование было следующим:

Комитет технической проверки Sun (TRC) подробно рассмотрел этот вопрос и рекомендовал не предпринимать никаких действий, кроме улучшения документации текущего интерфейса Cloneable . Вот полный текст рекомендации:

Существующие API клонирования объектов Java проблематичны. На java.lang.Object есть защищенный метод «клонирования» и есть интерфейс java.lang.Cloneable. Намерение состоит в том, что если класс хочет разрешить другим людям клонировать его, то он должен поддерживать интерфейс Cloneable и переопределять метод защищенного клонирования по умолчанию с помощью общедоступного метода клонирования. К сожалению, по причинам, которые удобно теряются в тумане времени, интерфейс Cloneable не определяет метод клонирования.

Эта комбинация приводит к изрядной путанице. Некоторые классы утверждают, что поддерживают Cloneable, но случайно забывают поддерживать метод clone. Разработчики не понимают, как должен работать Cloneable и что должен делать клон.

К сожалению, добавление метода «clone» в Cloneable было бы несовместимым изменением. Это не нарушит бинарную совместимость, но нарушит совместимость исходного кода. Неофициальные данные свидетельствуют о том, что на практике существует ряд случаев, когда классы поддерживают интерфейс Cloneable, но не могут предоставить общедоступный метод клонирования. После обсуждения TRC единодушно рекомендовал НЕ изменять существующий интерфейс Cloneable из-за влияния на совместимость.

Альтернативным предложением было добавить новый интерфейс java.lang.PubliclyCloneable, чтобы отразить первоначальное предназначение Cloneable. TRC большинством в 5 против 2 рекомендовал этого не делать. Основная проблема заключалась в том, что это добавит еще больше путаницы (в том числе путаницы с орфографией!) К уже запутанной картине.

TRC единодушно рекомендовал нам добавить дополнительную документацию к существующему интерфейсу Cloneable, чтобы лучше описать, как он предназначен для использования, и описать «лучшие практики» для разработчиков.

Итак, хотя речь идет не об устаревании , причина не делать Cloneable «устаревшим» заключается в том, что комитет по технической проверке решил, что изменения существующей документации будет достаточно, чтобы сделать этот интерфейс полезным. Так они и сделали. До Java 1.4 Cloneableбыло задокументировано следующее:

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

Попытки клонировать экземпляры, которые не реализуют интерфейс Cloneable, приводят к возникновению исключения CloneNotSupportedException.

Интерфейс Cloneable не объявляет никаких методов.

Начиная с версии Java 1.4 (выпущенной в феврале 2002 г.) до текущей версии (Java 8) это выглядит так:

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

По соглашению классы, реализующие этот интерфейс, должны переопределять Object.clone (который защищен) с помощью общедоступного метода. См. Object.clone () для получения подробной информации о переопределении этого метода.

Обратите внимание, что этот интерфейс не содержит метода clone. Следовательно, невозможно клонировать объект только в силу того факта, что он реализует этот интерфейс. Даже если метод clone вызывается рефлексивно, нет гарантии, что он будет успешным.

Као
источник
3
и знаете ли вы, почему этот cloneметод был Objectна первом месте?
njzk2
8
@ njzk2 Это важная часть механизма - метод, выполняющий низкоуровневую экстралингвистическую магию копирования изображения объекта бит за битом.
Марко Топольник
3
@Unheilig Эту магию нельзя воспроизвести с помощью конструктора копирования: Object#clone()создает экземпляр того же класса, что и оригинал, без того, чтобы этот класс был известен во время компиляции.
Марко Топольник
4
@AVolpe: Это не исправит несовместимость источников, упомянутую в ответе «где классы поддерживают интерфейс Cloneable, но не могут предоставить общедоступный метод клонирования». В частности, будут нарушены классы, предоставляющие закрытый метод клонирования.
Луи Вассерман
2
Хорошая история. Вот прямая ссылка на базу данных об ошибках. Я добавил туда еще немного истории и процитировал ее части в своем ответе .
Стюарт Маркс
64

Краткий ответ на вопрос "почему не Cloneableустарел?" (или, действительно, почему они не являются Xустаревшими X), заключается в том, что их устареванию не уделялось много внимания.

Большинство вещей, которые недавно были объявлены устаревшими, устарели, потому что существует специальный план их удаления. Например, методы addPropertyChangeListenerи LogManager были объявлены устаревшими в Java SE 8 с намерением удалить их в Java SE 9. (Причина в том, что они излишне усложняли взаимозависимости модулей). Действительно, эти API уже были удалены из ранней разработки JDK 9. строит. (Обратите внимание, что аналогичные вызовы прослушивателя изменения свойств также были удалены ; см. JDK-8029806 .)removePropertyChangeListenerPack200

Подобных планов не существует для Cloneableи Object.clone().

Более подробный ответ будет включать обсуждение дополнительных вопросов, например, что можно ожидать от этих API-интерфейсов, какие затраты или выгоды понесет платформа, если они станут устаревшими, и что сообщается разработчикам, когда API устаревает. Я исследовал эту тему в моем недавнем выступлении JavaOne « Долг и устаревание» . (Слайды доступны по этой ссылке; видео здесь .) Оказывается, сам JDK не очень последовательно использовал устаревание. Он использовался для обозначения нескольких разных вещей, в том числе, например,

  • Это опасно , и вы должны быть осведомлены о рисках , связанных с использованием (пример: Thread.stop(), Thread.resume(), и Thread.suspend()).

  • Это будет удалено в следующем выпуске

  • Это устарело, и вам рекомендуется использовать что-то другое (пример: многие методы в java.util.Date)

Все они имеют разные значения, и разные подмножества из них относятся к разным устаревшим вещам. И некоторые из них применимы к вещам, которые не являются устаревшими (но, возможно, их следует исключить).

Cloneableи Object.clone()являются «сломанными» в том смысле, что имеют конструктивные недостатки и их трудно правильно использовать. Однако clone()это по-прежнему лучший способ копирования массивов, и клонирование имеет ограниченную полезность для создания копий экземпляров тщательно реализованных классов. Удаление клонирования было бы несовместимым изменением, которое сломало бы многие вещи. Операцию клонирования можно было бы реализовать другим способом, но, вероятно, это будет медленнее, чем Object.clone().

Однако в большинстве случаев конструктор копирования предпочтительнее клонирования. Так что, возможно, Cloneableбудет уместна отметка «устаревшее» или «замененное» или что-то подобное. Это скажет разработчикам, что они, вероятно, захотят поискать в другом месте, но не будет сигналом того, что механизм клонирования может быть удален в будущем выпуске. К сожалению, такого маркера не существует.

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

ОБНОВИТЬ

Я добавил дополнительную историю в отчет об ошибке . Фрэнк Йеллин, один из первых разработчиков JVM и соавтор спецификации JVM, сделал несколько комментариев в ответ на комментарий «затерянный в тумане времени» в рекомендации TRC, цитируемый в другом ответе . Я процитировал здесь соответствующие части; полное сообщение находится в отчете об ошибке.

В Cloneable нет методов по той же причине, что и у Serializable. Cloneable указывает свойство класса, а не говорит что-либо конкретно о методах, поддерживаемых классом.

Перед отражением нам нужен был собственный метод для создания неглубокой копии объекта. Так родился Object.clone (). Также было ясно, что многие классы захотят переопределить этот метод и что не каждый класс захочет клонировать. Следовательно, Cloneable родился, чтобы указать на намерение программиста.

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

Стюарт Маркс
источник
3
У вас есть один прекрасный ответ, сэр. Мне особенно нравится, что вы не бросаете Object.clone()в огонь только потому, что все хотят этого, но вы готовы рассуждать и поднимать из этого положительные моменты.
icza
2
Однако clone () по-прежнему является лучшим способом копирования массивов, а клонирование имеет ограниченную полезность для создания копий экземпляров тщательно реализованных классов. Я был под впечатлением от исправления 6428387, все пути кода (clone, new / arrayCopy, Arrays.copyOf) привели к одним и тем же встроенным функциям. Что-нибудь изменилось за последнее время?
bestsss
2
@bestsss Я не думаю, что array.clone()это обязательно быстрее, чем любые альтернативы. С точки зрения API это наиболее лаконичный способ дублирования массива. Arrays.copyOf(array, newlen)подходит близко, но для этого требуется параметр длины, который является избыточным, если вы не меняете длину.
Стюарт Маркс,
2
@Holger Да, насколько мы видим, это первое фактическое удаление API с версии 1.1. Также обратите внимание, что даже если мы согласны с тем, что Thread.suspend()и Thread.stop()(no-arg) опасны, они, вероятно, не будут удалены - или изменены, чтобы безоговорочно генерировать исключение - потому что люди действительно их используют! Предположительно они готовы нести риск. Одним из смягчающих факторов со слушателями изменения свойств является то, что они использовались очень редко, поэтому их удаление незначительно.
Стюарт Маркс
2
@Holger Концептуально java.beansможно сделать независимым, java.desktopпоскольку beans - это всего лишь API библиотеки для свойств. К сожалению, если вы углубитесь в API beans, то увидите, что AWT сильно зависит от него. В реализации даже больше. Конечно, их можно было бы извлечь, но это кажется гораздо большей работой, чем, скажем, отделение журнала от beans. Все усилия по модуляции направлены на то, чтобы сделать это распутывание; несомненно, можно было бы сделать больше, но тогда Jigsaw потребовал бы еще больше времени.
Стюарт Маркс
-1

почему он еще не устарел?

Потому что JCP не считает нужным это делать и может никогда этого не сделать. Спроси их. Вы спрашиваете не в том месте.

Каковы причины сохранения этой штуки в Java API

Никто и никогда ничего не удалит из Java API из-за требований обратной совместимости. В последний раз это произошло при изменении модели событий AWT с 1.0 на 1.1 в 1996/7.

Маркиз Лорн
источник
17
Они (эффективно) удалили Thread.stop(Throwable), изменив свой контракт, чтобы всегда передавать UnsupportedOperationExceptionвызывающему (а не целевому потоку!).
Марко Топольник
22
Что произошло примерно в то же время? В Thread.stop(Throwable)Java 8 была удалена функциональность. В любом случае безоговорочный совет «спросить их» неверен, потому что сегодня сам главный архитектор Java является активным участником Stack Overflow. Он просто не удосуживается отвечать ни на что, кроме вопросов, связанных с Streams.
Марко Топольник
13
Кроме того, вопрос OP не об удалении , а об устаревании , и явно устаревание происходит все время.
Марко Топольник
17
@EJP Я не спрашиваю, Cloneableудалятся ли из Java API. Я спрашиваю, почему не собираются развращать. И я думаю, что это идеальное место, чтобы спросить.
Као
5
@VaheHarutyunyan Спасибо за привет, но я не архитектор Java. Я инженер в группе Oracle JDK, которая занимается этим.
Стюарт Маркс