Одним из преимуществ языка Java является то, что он отменяет концепцию указателей, но также заставляет многих программистов часто игнорировать разницу между объектами и ссылками в программировании. Эта статья попытается прояснить эту концепцию. И поскольку Java не может решить проблему копирования объекта посредством простого назначения, во время процесса разработки, часто необходимо использовать метод Clone () для копирования объектов. Эта статья позволит вам понять, что такое теневой клон и глубокий клон, и понять их различия, преимущества и недостатки.
Вы немного смущены, когда видите это название: язык Java четко заявляет, что указатели отменяются, потому что указатели часто удобны, а также основная причина отсутствия кода, а также делает программу очень сложной и трудной для понимания. Код, написанный злоупотреблением указателями, является не меньше, чем использование и без того печально известного заявления «Goto». Для Java абсолютно разумно отказаться от концепции указателей. Но это просто то, что на языке Java нет четкого определения указателя. По сути, каждое новое утверждение возвращает ссылку на указатель. Однако в большинстве случаев Java не нужно беспокоиться о том, как управлять этим «указателем», не говоря уже о том, чтобы быть таким же пугающим, как при эксплуатации указателей в C ++. Единственное, о чем нужно больше заботиться, это при передаче объектов функциям.
пакет com.zoer.src; открытый класс objref {obj aobj = new obj (); int in int = 11; public void changeObj (obj inobj) {inobj.str = "Измененное значение"; } public void changePri (int inint) {inint = 22; } public static void main (string [] args) {objref oref = new objref (); System.out.println ("Перед вызовом method:" + oref.aobj); oref.changeobj (oref.aobj); System.out.println ("После вызова mediceobj () метод:" + oref.aobj); System.out.println ("================================================================================== ============================================================================================================= ============================================================================================================= ============================================================================================================= Call indomepripri () Метод: " + oref.aint); }} пакет com.zoer.src; открытый класс obj {string str = "init value"; public String toString () {return str; }} Основная часть этого кода вызывает два очень похожих метода, changeObj () и changePRI (). Единственное отличие состоит в том, что один принимает объект в качестве входного параметра, а другой принимает базовый тип int в Java в качестве входного параметра. И входные параметры изменяются внутри обоих функций. Казалось бы, тот же метод, но выходные результаты программы различны. Метод изменения oblobj () действительно изменяет входные параметры, в то время как метод изменения PREECHPRI () не изменяет входные параметры.
Из этого примера Java обрабатывает объекты и основные типы данных по -разному. Как C, когда основные типы данных Java (такие как Int, char, Double и т. Д.) передаются в тело функции в качестве параметров записи, передача параметров становятся локальными переменными внутри корпуса функции. Эта локальная переменная является копией входных параметров. Все операции внутри корпуса функции являются операциями для этой копии. После того, как выполнение функции будет завершено, локальная переменная завершит свою миссию, и она не повлияет на переменные в качестве входных параметров. Этот метод передачи параметров называется «прохождение значения». В Java проход с использованием объекта в качестве параметра входа является «ссылочным проходом», что означает, что передается только одна «ссылка» объекта. Концепция этой «ссылки» такая же, как и ссылка на указатель на языке C. Когда входная переменная изменяется внутри корпуса функции, она по сути является прямой работой на объекте.
За исключением «Справочного прохода» при прохождении значения функции «Переход» при назначении значений переменным объекта с использованием "=". Это похоже на то, чтобы дать переменную еще один псевдоним. Оба имена указывают на один и тот же объект в памяти.
В реальном программировании мы часто сталкиваемся с этой ситуацией: есть объект A, который содержит некоторые допустимые значения в определенный момент. В настоящее время может потребоваться новый объект B точно так же, как A, и любые изменения в B не будут влиять на значение в A. то есть A и B являются двумя независимыми объектами, но начальное значение B определяется объектом A. На языке Java использование простых заявлений о назначении не может соответствовать этому требованию. Хотя есть много способов удовлетворить эту потребность, самый простой и наиболее эффективный способ реализации метода клона () является среди них.
Все классы Java наследуют класс java.lang.object по умолчанию, и в классе java.lang.object есть клон (). Документация JDK API объясняет, что этот метод вернет копию объекта объекта. Есть два пункта, которые нужно объяснить: во -первых, объект копирования возвращает новый объект, а не ссылку. Во -вторых, разница между копированием объекта и новым объектом, возвращенным с новым оператором, заключается в том, что эта копия уже содержит некоторую информацию о исходном объекте, а не начальную информацию объекта.
Как применить метод клона ()?
Очень типичный призыв к коду Clone () заключается в следующем:
Общедоступный класс CloneClass реализует клонируемые {public int in int; public Object Clone () {cloneclass o = null; try {o = (cloneclass) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} Есть три очка, которые стоит отметить. Во -первых, класс CloneClass, который надеется реализовать функцию клона, реализует клонируемый интерфейс. Этот интерфейс принадлежит пакету java.lang. Пакет Java.lang был импортирован в класс по умолчанию, поэтому его не нужно писать как java.lang.clonable. Еще одна вещь, которую стоит отметить, это то, что метод клона () перегружен. Наконец, Super.Clone () вызывается в методе Clone (), что также означает, что независимо от того, как выглядит структура наследования класса клонов, Super.Clone () прямо или косвенно называет метод клона () класса java.lang.object. Давайте объясним эти моменты подробно ниже.
Следует сказать, что третий пункт является наиболее важным. Если вы тщательно соблюдаете нативный метод клона класса объекта (), эффективность нативного метода, как правило, намного выше, чем у неродных методов в Java. Это также объясняет, почему мы должны использовать метод Clone () в объекте вместо сначала нового класса, а затем назначать информацию из исходного объекта новому объекту, хотя это также реализует функцию клона. Для второго пункта мы также должны наблюдать, является ли Clone () в классе объекта методом с защищенным свойством. Это также означает, что если вы хотите применить метод Clone (), вы должны унаследовать класс объекта. В Java все классы по умолчанию наследуют класс объектов, поэтому вам не нужно беспокоиться об этом. Затем перегружайте метод клона (). Еще одна вещь, которую следует учитывать, это то, что для того, чтобы другие классы вызывали метод клона () этого класса клонов после перегрузки, атрибуты метода клона () должны быть установлены на публику.
Так почему же класс клонов все еще реализует клонируемый интерфейс? Обратите внимание, что клонируемый интерфейс не содержит никаких методов! На самом деле, этот интерфейс является всего лишь флагом, и этот флаг предназначен только для метода клона () в классе объекта. Если класс клонов не реализует клонируемый интерфейс и вызывает метод Clone () Clone (то есть метод Super.Clone (), то метод объекта Clone () будет выбросить исключение CloneNotSUpportException.
Вышеуказанное - самые основные шаги клона. Если вы хотите завершить успешный клон, вам также нужно понять, что такое «теневой клон» и «глубокий клон».
Что такое теневой клон?
пакет com.zoer.src; класс unclonea {private int i; public unclonea (int ii) {i = ii; } public void DoubleValue () {i *= 2; } public String toString () {return integer.toString (i); }} класс Cloneb реализует клонируемые {public int int; public unclonea unca = new unclonea (111); public Object Clone () {cloneb o = null; try {o = (cloneb) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} открытый класс objref {public static void main (string [] a) {cloneb b1 = new cloneb (); B1.And = 11; System.out.println («До клона, b1.aint =" + b1.aint); System.out.println («До клона, b1.unca =" + b1.unca); CLONEB B2 = (CLONEB) B1.Clone (); b2.aint = 22; b2.unca.doublevalue (); System.out.println ("================================================================================================================= ===================================================================== ======================================================================= ===================================================================== ======================================================================= ===================================================================== ======================================================================= клон, b2.aint = " + b2.aint); System.out.println ("После клона, b2.unca =" + b2.unca); }} Результат вывода:
Перед клоном, b1.aint = 11 -й клон, b1.unca = 111 ================================================================== ====================================================================== ===================================================================== ====================================================================== ===================================================================== ====================================================================== ===================================================================== ======================================================================
Результаты выходных результатов показывают, что переменная int не nit и результат клона объекта экземпляра Unca unclonea не соответствует. Тип INT действительно клон, потому что переменная utint в B2 не влияет на не B1. То есть b2.aint и b1.aint уже занимают разные пространства памяти, b2.aint - это реальная копия B1.aint. Напротив, изменение на B2.unca изменяется одновременно на B1.unca, и очевидно, что B2.unca и B1.unca - это разные ссылки, которые указывают только на один и тот же объект! Исходя из этого, мы видим, что эффект вызова метода Clone () в классе объекта: сначала откройте кусок пространства в памяти, который такой же, как исходный объект, а затем скопируйте содержимое в исходном объекте, как оно есть. Для основных типов данных такие операции не являются проблематичными, но для неприменения переменных типов мы знаем, что они сохраняют только ссылки на объекты, что также вызывает непримененные переменные типа после клона указывать на один и тот же объект и соответствующие переменные в исходном объекте.
Большую часть времени результаты этого клона часто не являются теми результатами, на которые мы надеемся, и этот клон также называется «теневым клоном». Чтобы сделать B2.Unca указывать на объект, отличный от B2.unca, и B2.unca также содержит информацию в B1.unca в качестве начальной информации, вы должны реализовать клон глубины.
Как выполнить глубокий клон?
Очень просто изменить приведенный выше пример на глубокий клон, и требуются два изменения: одно состоит в том, чтобы позволить классу Unclonea также реализовать ту же функцию клона, что и класс CloneB (реализуйте интерфейс клонируемого и перегруженную методом клона ()). Второе - добавить предложение o.unca = (unclonea) unca.clone () к методу клона () клонеб;
пакет com.zoer.src; Класс Unclonea реализует клонируемый {private int i; public unclonea (int ii) {i = ii; } public void DoubleValue () {i *= 2; } public String toString () {return integer.toString (i); } public Object Clone () {unclonea o = null; try {o = (unclonea) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} класс Cloneb реализует клонируемые {public int int; public unclonea unca = new unclonea (111); public Object Clone () {cloneb o = null; try {o = (cloneb) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } o.unca = (unclonea) unca.clone (); вернуть O; }} public Class clonemain {public static void main (string [] a) {cloneb b1 = new cloneb (); B1.And = 11; System.out.println («До клона, b1.aint =" + b1.aint); System.out.println («До клона, b1.unca =" + b1.unca); CLONEB B2 = (CLONEB) B1.Clone (); b2.aint = 22; b2.unca.doublevalue (); System.out.println ("================================================================================= ========================================================================================== ========================================================================================== ========================================================================================== клон, b1.aint = " + b1.aint); System.out.println ("После клона, b1.unca =" + b1.unca); System.out.println ("========================================================================================== ==================================================================== ===================================================================== ==================================================================== ===================================================================== ==================================================================== ===================================================================== Результат вывода:
Перед клоном, b1.aint = 11 -й клон, b1.unca = 111 ================================================================== ====================================================================== ===================================================================== ====================================================================== ===================================================================== ====================================================================== ===================================================================== ======================================================================
Можно видеть, что текущее изменение B2.Unca не влияет на B1.unca. В это время b1.unca и b2.unca указывают на два разных экземпляра Unclonea, а B1 и B2 имеют одинаковое значение в тот момент, когда Cloneb b2 = (cloneb) b1.clone (); Здесь называется b1.i = b2.i = 11.
Вы должны знать, что не все классы могут реализовать глубокие клоны. Например, если вы измените переменную типа Unclonea в классе Cloneb выше на тип StringBuffer, посмотрите на описание о StringBuffer в API JDK. StringBuffer не перегружает метод Clone (), и что более серьезно, так это то, что StringBuffer по -прежнему является окончательным классом, что означает, что мы не можем косвенно реализовать клон StringBuffer, используя наследование. Если класс содержит объект типа StringBuffer или объект, похожий на StringBuffer, у нас есть два варианта: либо может быть реализован только клон тени, либо добавить предложение к методу клона () класса (предполагая, что это объект SringBuffer, а имя переменной все еще - unca): o.unca = new StringBuffer (unca.ToString ()); // оригинал: o.unca = (unclonea) unca.clone ();
Следует также отметить, что в дополнение к основным типам данных, которые могут автоматически реализовать глубокие клоны, объект String является исключением. Его производительность после клона, кажется, также внедряет глубокие клоны. Хотя это просто иллюзия, это значительно облегчает наше программирование.
Разница между String и StringBuffer в клоне следует объяснить, что это не фокусируется на разнице между String и StringBuffer, но из этого примера мы также можем увидеть некоторые уникальные особенности класса строки.
Следующий пример включает два класса. Класс Clonec содержит переменную типа строки и переменную типа StringBuffer и реализует метод Clone (). Переменная типа клонека C1 объявлена в классе Strclone, а затем метод Clone () C1 вызван для создания копии C1 C2. После изменения переменных типа строки и типа StringBuffer в C2, результат напечатан:
пакет com.zoer.src; Класс Clonec реализует клонируемый {public String Str; public Stringbuffer strbuff; public Object Clone () {clonec o = null; try {o = (clonec) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} открытый класс strclone {public static void main (string [] a) {clonec c1 = new clonec (); c1.str = new String ("initiazestr"); c1.strbuff = new Stringbuffer ("initializestrbuff"); System.out.println («До клона, c1.str =" + c1.str); System.out.println ("До клона, c1.strbuff =" + c1.strbuff); Clonec c2 = (clonec) c1.clone (); c2.str = c2.str.substring (0, 5); c2.strbuff = c2.strbuff.append («Изменить клон Strbuff»); System.out.println ("========================================================================= ======================================================================= ======================================================================= ======================================================================== ======================================================================= ======================================================================= ======================================================================= ======================================================================== System.out.println ("После клона, c2.strbuff =" + c2.strbuff); Результаты исполнения:
<span style = "font-family: 'microsoft yahei';"> <span style = "font-size: 16px;"> Перед клоном, c1.str = initizezest перед клоном, c1.strbuff = initiazestrbuffuffuffufful ============================================================================================= ============================================================================================= ============================================================================================= ============================================================================================= клон, c2.str = initiazestrbuff изменение клона Strbuff </span> </span>
Печатный результат показывает, что переменные типа строки, похоже, реализовали глубинный клон, потому что изменения в C2.str не повлияли на C1.str! Считает ли Java класс SRING как основной тип данных? На самом деле, это не так. Здесь есть небольшой трюк. Секрет заключается в заявлении C2.str = C2.str.Substring (0,5)! По сути, C1.str и C2.str все еще являются ссылками на клон, и оба указывают на один и тот же строковый объект. Но когда c2.str = c2.str.substring (0,5), он эквивалентен генерации нового типа строки, а затем назначает его обратно C2.str. Это связано с тем, что строка написана как неизменное класс инженерами Sun, а функции во всех классах строк не могут изменить свои собственные значения.
Выше всего об этой статье. Я надеюсь, что для всех будет полезно понять глубокую и мелкую репликацию на Java.