Una ventaja del lenguaje Java es que cancela el concepto de punteros, pero también lleva a muchos programadores a ignorar la diferencia entre objetos y referencias en la programación. Este artículo intentará aclarar este concepto. Y debido a que Java no puede resolver el problema de la copia de los objetos a través de una asignación simple, durante el proceso de desarrollo, a menudo es necesario usar el método Clone () para copiar objetos. Este artículo le permitirá comprender qué son Shadow Clone y Deep Clone, y comprender sus diferencias, ventajas y desventajas.
¿Estás un poco confundido cuando ve este título? El lenguaje Java establece claramente que los punteros se cancelan, porque los punteros a menudo son convenientes y también la causa raíz de la inseguridad del código, y también hace que el programa sea muy complejo y difícil de entender. El código escrito por el abuso de punteros no es menos que usar la ya infame declaración "goto". Es absolutamente sabio que Java renuncie al concepto de punteros. Pero esto es solo que no hay una definición clara de puntero en el idioma Java. En esencia, cada nueva declaración devuelve una referencia de puntero. Sin embargo, en la mayoría de los casos, Java no necesita preocuparse por cómo operar este "puntero", y mucho menos estar tan asustado como cuando operan punteros en C ++. Lo único de lo que se preocupa más es cuando se pasa objetos a las funciones.
paquete com.zoer.src; clase pública objref {obj aobj = new obj (); int aint = 11; public void ChangeObj (obj inobj) {inobj.str = "valor cambiado"; } public void ChangePri (int inIt) {inInt = 22; } public static void main (string [] args) {objref oref = new objref (); System.out.println ("Antes del método de changeobj ():" + oref.aobj); oref.changeObj (oref.aobj); System.out.println ("Después del método de llamada de changeObj ():" + oref.aobj); System.out.println("=============================================================================================== ==================================================================================================================== ==================================================================================================================== ==================================================================================================================== Llame al método ChangePri (): " + Oref.aint); }} paquete com.zoer.src; clase pública obj {string str = "valor init"; public String toString () {return str; }} La parte principal de este código llama a dos métodos muy similares, ChangeOBJ () y ChangePri (). La única diferencia es que uno toma el objeto como el parámetro de entrada, y el otro toma el tipo básico int en Java como el parámetro de entrada. Y los parámetros de entrada se cambian dentro de ambos cuerpos de funciones. El método aparentemente el mismo, pero los resultados de salida del programa son diferentes. El método ChangeOBJ () realmente cambia los parámetros de entrada, mientras que el método ChangePri () no cambia los parámetros de entrada.
A partir de este ejemplo, Java procesa objetos y tipos de datos básicos de manera diferente. Al igual que C, cuando los tipos de datos básicos de Java (como int, char, double, etc.) se pasan al cuerpo de la función como parámetros de entrada, los parámetros pasados se convierten en variables locales dentro del cuerpo de la función. Esta variable local es una copia de los parámetros de entrada. Todas las operaciones dentro del cuerpo de la función son operaciones para esta copia. Una vez completada la ejecución de la función, la variable local completará su misión y no afectará las variables como parámetros de entrada. Este método de paso de parámetros se llama "pase de valor". En Java, el pase que usa un objeto como parámetro de entrada es "Pase de referencia", lo que significa que solo se pasa una "referencia" del objeto. El concepto de esta "referencia" es el mismo que la referencia del puntero en el lenguaje C. Cuando la variable de entrada se cambia dentro del cuerpo de la función, es esencialmente una operación directa en el objeto.
Excepto para "Pase de referencia" al pasar el valor de la función, "Pase de referencia" al asignar valores a las variables de objeto usando "=". Es similar a dar a una variable otro alias. Ambos nombres apuntan al mismo objeto en la memoria.
En la programación real, a menudo encontramos esta situación: hay un objeto A que contiene algunos valores válidos en un momento determinado. En este momento, un nuevo objeto B puede ser necesario exactamente igual que A, y cualquier cambio a B no afectará el valor en A. Es decir, A y B son dos objetos independientes, pero el valor inicial de B está determinado por el objeto A. En el lenguaje Java, el uso de declaraciones de asignación simples no puede cumplir con este requisito. Aunque hay muchas formas de satisfacer esta necesidad, la forma más simple y eficiente de implementar el método Clone () está entre ellos.
Todas las clases de Java heredan la clase java.lang.object de forma predeterminada, y hay un método clone () en la clase java.lang.object. La documentación de la API JDK explica que este método devolverá una copia del objeto objeto. Hay dos puntos por explicar: primero, el objeto de copia devuelve un nuevo objeto, no una referencia. En segundo lugar, la diferencia entre copiar un objeto y un nuevo objeto devuelto con el nuevo operador es que esta copia ya contiene información sobre el objeto original, en lugar de la información inicial del objeto.
¿Cómo aplicar el método Clone ()?
Una llamada muy típica al código Clone () es el siguiente:
clase pública cloneclass implementa clonable {public int Aint; Public Object clone () {Cloneclass o = null; intente {o = (cloneclass) super.clone (); } Catch (ClonenotsupportedException e) {E.PrintStackTrace (); } return o; }} Hay tres puntos que vale la pena señalar. Primero, la clase Cloneclass que espera implementar la función Clone implementa la interfaz clonable. Esta interfaz pertenece al paquete java.lang. El paquete java.lang se ha importado a la clase predeterminada, por lo que no es necesario escribir como java.lang.clonable. Otra cosa que vale la pena señalar es que el método Clone () está sobrecargado. Finalmente, Super.Clone () se llama en el método Clone (), lo que también significa que no importa cómo se ve la estructura de herencia de la clase Clone, super.clone () directa o indirectamente llama al método clone () de la clase java.lang.object. Expliquemos estos puntos en detalle a continuación.
Debe decirse que el tercer punto es el más importante. Si observa cuidadosamente un método nativo del clon de clase de objeto (), la eficiencia del método nativo es generalmente mucho mayor que la de los métodos no nativos en Java. Esto también explica por qué debemos usar el método Clone () en el objeto en lugar de la primera clase A y luego asignar la información del objeto original al nuevo objeto, aunque esto también implementa la función Clone. Para el segundo punto, también debemos observar si Clone () en la clase de objeto es un método con una propiedad protegida. Esto también significa que si desea aplicar el método Clone (), debe heredar la clase de objeto. En Java, todas las clases heredan la clase de objeto de forma predeterminada, por lo que no tiene que preocuparse por esto. Luego sobrecarga el método Clone (). Otra cosa a considerar es que para que otras clases llamen al método Clone () de esta clase de clon, después de la sobrecarga, los atributos del método Clone () deben establecerse en público.
Entonces, ¿por qué la clase Clone todavía implementa la interfaz clonable? ¡Tenga en cuenta que la interfaz clonable no contiene ningún método! De hecho, esta interfaz es solo una bandera, y este indicador es solo para el método Clone () en la clase de objeto. Si la clase Clone no implementa la interfaz clonable y llama al método Clone () del objeto (es decir, el método súper.clone () se llama), entonces el método clon () del objeto lanzará una excepción de clonEnotsupportedException.
Los anteriores son los pasos más básicos del clon. Si desea completar un clon exitoso, también debe comprender qué es el "clon de sombra" y el "clon profundo".
¿Qué es Shadow Clone?
paquete com.zoer.src; clase unclonea {private int i; public Unclonea (int ii) {i = ii; } public void doubleValue () {i *= 2; } public String toString () {return Integer.ToString (i); }} Clase cloneb implementa clonable {public int aint; público unclonea unga = new Unclonea (111); Public Object clone () {cloneb o = null; intente {o = (cloneb) super.clone (); } Catch (ClonenotsupportedException e) {E.PrintStackTrace (); } return o; }} clase pública objref {public static void main (string [] a) {cloneb b1 = new cloneb (); B1.Aint = 11; System.out.println ("antes de clon, b1.aint =" + b1.aint); System.out.println ("antes del clon, b1.unca =" + b1.unca); Cloneb b2 = (cloneb) b1.clone (); b2.Aint = 22; B2.UNTA.DOUBLEVALUE (); System.out.println("======================================================================================================== ================================================================= ================================================================== ================================================================= ================================================================== ================================================================= ================================================================== clon, b2.aint = " + b2.aint); System.out.println ("después del clon, b2.unca =" + b2.unca); }} Resultado de salida:
Antes del clon, b1.aint = 11before clon, b1.unca = 111 ============================================================= ================================================================ ============================================================== ================================================================ ============================================================== ================================================================ ============================================================== ================================================================
Los resultados de la salida muestran que la variable int y el resultado del clon del objeto de instancia una de Unclonea son inconsistentes. El tipo int es realmente clon porque la variable AINT en B2 no tiene ningún efecto en el Aint de B1. Es decir, B2.Aint y B1.Aint ya ocupa diferentes espacios de memoria, B2.Aint es una copia real de B1.AINT. Por el contrario, el cambio a B2.UNTA cambia simultáneamente a B1.UNCA, ¡y es obvio que B2.UNTA y B1.UNCA son diferentes referencias que solo apuntan al mismo objeto! A partir de esto, podemos ver que el efecto de llamar al método Clone () en la clase de objeto es: primero abrir una pieza de espacio en la memoria que sea el mismo que el objeto original, y luego copiar el contenido en el objeto original tal como está. Para los tipos de datos básicos, tales operaciones no son problemáticas, sino para las variables de tipo no probitivos, sabemos que solo guarda referencias a los objetos, lo que también hace que las variables de tipo no probitivas después del clon apunten al mismo objeto y las variables correspondientes en el objeto original.
La mayoría de las veces, los resultados de este clon a menudo no son los resultados que esperamos, y este clon también se llama "clon de sombra". Para hacer que B2.UNCA apunte a un objeto diferente de B2.UNCA, y B2.UNTA también contiene información en B1.UNCA como información inicial, debe implementar un clon de profundidad.
¿Cómo realizar un clon profundo?
Es muy simple cambiar el ejemplo anterior a un clon profundo, y se requieren dos cambios: uno es permitir que la clase de Unclonea también implementa la misma función clon que la clase cloneb (implementa la interfaz clonable y sobrecarga el método clone ()). El segundo es agregar una oración o.unca = (unclonea) unca.clone () al método clone () de cloneb;
paquete com.zoer.src; clase Unclonea implementa clonable {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; intente {o = (unclonea) super.clone (); } Catch (ClonenotsupportedException e) {E.PrintStackTrace (); } return o; }} Clase cloneb implementa clonable {public int aint; público unclonea unga = new Unclonea (111); Public Object clone () {cloneb o = null; intente {o = (cloneb) super.clone (); } Catch (ClonenotsupportedException e) {E.PrintStackTrace (); } O.UNCA = (UNCLONEA) unca.clone (); regreso o; }} public class Clonemain {public static void main (string [] a) {cloneb b1 = new cloneb (); B1.Aint = 11; System.out.println ("antes de clon, b1.aint =" + b1.aint); System.out.println ("antes del clon, b1.unca =" + b1.unca); Cloneb b2 = (cloneb) b1.clone (); b2.Aint = 22; B2.UNTA.DOUBLEVALUE (); System.out.println("================================================================================ ================================================================================================================================================================================================. ================================================================================================================================================================================================. =============================================================================================================================================================================================================. clon, b1.aint = " + b1.aint); System.out.println ("después del clon, b1.unca =" + b1.unca); System.out.println("=================================================================================================== ============================================================= ============================================================== ============================================================= ============================================================== ============================================================= ============================================================== Resultado de salida:
Antes del clon, b1.aint = 11before clon, b1.unca = 111 ============================================================= ================================================================ ============================================================== ================================================================ ============================================================== ================================================================ ============================================================== ================================================================
Se puede ver que el cambio actual de B2.UNTA no tiene ningún efecto en B1.UNCA. En este momento, B1.UNCA y B2.UNTA apuntan a dos instancias diferentes de Unclonea, y B1 y B2 tienen el mismo valor en el momento en que Cloneb B2 = (Cloneb) B1.Clone (); se llama, aquí, b1.i = b2.i = 11.
Debe saber que no todas las clases pueden implementar clones profundos. Por ejemplo, si cambia la variable de tipo Unclonea en la clase Cloneb anterior al tipo StringBuffer, mire la descripción sobre StringBuffer en la API JDK. StringBuffer no sobrecarga el método Clone (), y lo que es más serio es que StringBuffer sigue siendo una clase final, lo que significa que no podemos implementar indirectamente el clon de StringBuffer usando la herencia. Si una clase contiene un objeto de tipo StringBuffer o un objeto similar a StringBuffer, tenemos dos opciones: solo se puede implementar el clon de sombra, o agregar una oración al método clone () de la clase (suponiendo que es un objeto sringbuffer, y el nombre de la variable aún es unca): o.unca = new StringBuffer (unca.tostring ()); // El original es: o.unca = (unclonea) unca.clone ();
También se debe tener en cuenta que, además de los tipos de datos básicos que pueden implementar automáticamente clones profundos, el objeto de cadena es una excepción. Su rendimiento después del clon parece también implementar clones profundos. Aunque esto es solo una ilusión, facilita enormemente nuestra programación.
La diferencia entre String y StringBuffer en Clone debe explicarse que esto no es un enfoque en la diferencia entre String y StringBuffer, pero a partir de este ejemplo, también podemos ver algunas características únicas de la clase String.
El siguiente ejemplo incluye dos clases. La clase Clonec contiene una variable de tipo de cadena y una variable de tipo StringBuffer, e implementa el método Clone (). La variable de tipo Clonec C1 se declara en la clase StrClone, y luego se llama al método Clone () de C1 para generar una copia de C1 C2. Después de cambiar las variables de tipo String y StringBuffer en C2, se imprime el resultado:
paquete com.zoer.src; clase Clonec implementa clonable {public String Str; Public StringBuffer Strbuff; Public Object clone () {clonec o = null; intente {o = (clonec) super.clone (); } Catch (ClonenotsupportedException e) {E.PrintStackTrace (); } return o; }} public class StrClone {public static void main (string [] a) {clonec c1 = new clonec (); c1.str = new String ("Initializestr"); c1.strbuff = new StringBuffer ("InitializeStrbuff"); System.out.println ("antes de clon, c1.str =" + c1.str); System.out.println ("antes de clon, c1.strbuff =" + c1.strbuff); Clonec c2 = (clonec) c1.clone (); c2.str = c2.str.substring (0, 5); c2.strbuff = c2.strbuff.append ("cambiar strbuff clone"); System.out.println ("================================================ =================================================================== =================================================================== ===================================================================== =================================================================== =================================================================== =================================================================== ===================================================================== System.out.println ("Después de clon, c2.strbuff =" + c2.strbuff); Resultados de la ejecución:
<span style = "Font-Family: 'Microsoft Yahei';"> <span style = "font-size: 16px;"> antes de clon, c1.str = inicializestrest antes de clon, c1.strbuff = inicializestrbuff ============================================================================================================== ============================================================================================================== ============================================================================================================== ============================================================================================================== Clone, C2.str = InitializeStrbuff Change Strbuff Clone </span> </span>
El resultado impreso muestra que las variables del tipo de cadena parecen haber implementado el clon de profundidad, porque los cambios en C2.STR no han afectado a C1.STR! ¿Java considera la clase de Sring como un tipo de datos básicos? En realidad, este no es el caso. Hay un pequeño truco aquí. ¡El secreto se encuentra en la declaración c2.str = c2.str.substring (0,5)! En esencia, C1.STR y C2.str son referencias cuando el clon, y ambos apuntan al mismo objeto de cadena. Pero cuando c2.str = c2.str.substring (0,5), es equivalente a generar un nuevo tipo de cadena y luego asignarlo nuevamente a C2.str. Esto se debe a que la cadena está escrita como una clase inmutable por los ingenieros de Sun, y las funciones en todas las clases de cadena no pueden cambiar sus propios valores.
Lo anterior se trata de este artículo. Espero que sea útil para todos comprender la replicación profunda y superficial en Java.