En la programación orientada a objetos , la copia de objetos consiste en crear una copia de un objeto existente , una unidad de datos en la programación orientada a objetos. El objeto resultante se denomina copia del objeto o simplemente copia del objeto original. La copia es un proceso básico, pero tiene sus particularidades y puede generar una sobrecarga considerable. Existen varias formas de copiar un objeto, siendo las más comunes el constructor de copia o la clonación . La copia se realiza principalmente para que la copia pueda modificarse, moverse o conservar su valor actual. Si no se requiere ninguna de estas acciones, una referencia a los datos originales es suficiente y más eficiente, ya que no se produce ninguna copia.
En general, los objetos almacenan datos compuestos . Si bien en casos sencillos la copia se puede realizar asignando un objeto nuevo y no inicializado y copiando todos los campos ( atributos ) del objeto original, en casos más complejos esto no produce el comportamiento deseado.
Métodos de copia
El objetivo de diseño de la mayoría de los objetos es dar la impresión de estar hechos de un solo bloque monolítico, aunque en realidad no lo estén. Dado que los objetos se componen de varias partes diferentes, copiarlos se vuelve una tarea compleja. Existen diversas estrategias para abordar este problema.
Consideremos un objeto A, que contiene campos x i (más concretamente, consideremos que A es una cadena y x i es un array de sus caracteres). Existen diferentes estrategias para hacer una copia de A, denominadas copia superficial y copia profunda . Muchos lenguajes permiten la copia genérica mediante una o ambas estrategias, definiendo una única operación de copia o bien operaciones separadas de copia superficial y profunda . [ 1 ] Cabe destacar que una copia aún más superficial consiste en utilizar una referencia al objeto A existente, en cuyo caso no se crea un nuevo objeto, sino solo una nueva referencia.
La terminología de copia superficial y copia profunda se remonta a Smalltalk -80. [ 2 ] La misma distinción se aplica a la comparación de objetos para determinar la igualdad: básicamente, existe una diferencia entre identidad (mismo objeto) e igualdad (mismo valor), que corresponde a la igualdad superficial y la igualdad profunda (de un nivel) de dos referencias a objetos, pero luego surge la cuestión de si la igualdad significa comparar solo los campos del objeto en cuestión o desreferenciar algunos o todos los campos y comparar sus valores a su vez (por ejemplo, ¿son iguales dos listas enlazadas si tienen los mismos nodos o si tienen los mismos valores?).
Copia superficial



Un método para copiar un objeto es la copia superficial . En ese caso, se crea un nuevo objeto B y los valores de los campos de A se copian a B. [ 3 ] [ 4 ] [ 5 ] Esto también se conoce como copia campo por campo , [ 6 ] [ 7 ] [ 8 ] copia campo por campo o copia de campo . [ 9 ] Si el valor del campo es una referencia a un objeto (por ejemplo, una dirección de memoria ), copia la referencia, por lo que se refiere al mismo objeto que A, y si el valor del campo es un tipo primitivo, copia el valor del tipo primitivo. En lenguajes sin tipos primitivos (donde todo es un objeto), todos los campos de la copia B son referencias a los mismos objetos que los campos del original A. Los objetos referenciados son, por lo tanto, compartidos , de modo que si uno de estos objetos se modifica (desde A o B), el cambio es visible en el otro. Las copias superficiales son simples y generalmente económicas, ya que normalmente se pueden implementar simplemente copiando los bits exactamente.
Copia profunda
Una alternativa es la copia profunda, lo que significa que los campos se desreferencian: en lugar de copiar las referencias a los objetos, se crean nuevas copias de los objetos referenciados y las referencias a estos se colocan en B. Las modificaciones posteriores al contenido siguen siendo exclusivas de A o B, ya que el contenido no se comparte.
Combinación
En casos más complejos, algunos campos de una copia deben tener valores compartidos con el objeto original (como en una copia superficial), lo que corresponde a una relación de "asociación"; algunos campos deben tener copias (como en una copia profunda), lo que corresponde a una relación de "agregación". En estos casos, generalmente se requiere una implementación personalizada de la copia; este problema y su solución se remontan a Smalltalk-80. [ 10 ] Alternativamente, los campos pueden marcarse para que requieran una copia superficial o profunda, y las operaciones de copia se generan automáticamente (igualmente para las operaciones de comparación). [ 1 ] Sin embargo, esto no está implementado en la mayoría de los lenguajes orientados a objetos, aunque hay soporte parcial en Eiffel. [ 1 ]
Implementación
Casi todos los lenguajes de programación orientados a objetos ofrecen alguna forma de copiar objetos. Dado que la mayoría de los lenguajes no proporcionan la mayoría de los objetos para los programas, el programador debe definir cómo se debe copiar un objeto, del mismo modo que debe definir si dos objetos son idénticos o incluso comparables. Muchos lenguajes ofrecen un comportamiento predeterminado.
La forma en que se resuelve el problema de la copia varía de un idioma a otro, y también depende del concepto de objeto que se tenga en él.
Copia perezosa
La copia diferida es una implementación de la copia profunda. Al copiar inicialmente un objeto, se utiliza una copia superficial (rápida). También se usa un contador para controlar cuántos objetos comparten los datos. Cuando el programa quiere modificar un objeto, puede determinar si los datos son compartidos (consultando el contador) y realizar una copia profunda si es necesario.
La copia perezosa se comporta externamente como una copia profunda, pero aprovecha la velocidad de una copia superficial siempre que sea posible. La desventaja radica en unos costes base relativamente altos pero constantes debido al contador. Además, en ciertas situaciones, las referencias circulares pueden causar problemas.
La copia perezosa está relacionada con la copia en escritura .
En C++ , se produce una copia diferida por defecto al invocar el constructor de copia .
En Java
A continuación se presentan ejemplos para uno de los lenguajes orientados a objetos más utilizados, Java , que deberían abarcar casi todas las formas en que un lenguaje orientado a objetos puede abordar este problema.
A diferencia de C++, en Java los objetos siempre se acceden indirectamente a través de referencias . Los objetos nunca se crean implícitamente, sino que siempre se pasan o asignan mediante una variable de referencia. (Los métodos en Java siempre se pasan por valor ; sin embargo, es el valor de la variable de referencia el que se pasa). [ 11 ] La máquina virtual de Java gestiona la recolección de basura para que los objetos se eliminen una vez que ya no son accesibles. No existe una forma automática de copiar un objeto dado en Java.
La copia se suele realizar mediante el método `clone()` de una clase. Este método, a su vez, llama al método `clone()` de su clase padre para obtener una copia y, a continuación, ejecuta cualquier procedimiento de copia personalizado. Finalmente, esto llega al método `clone()` de Objectla clase superior, que crea una nueva instancia de la misma clase que el objeto y copia todos los campos a la nueva instancia (una "copia superficial"). Si se utiliza este método, la clase debe implementar la Cloneableinterfaz `marker`; de lo contrario, se producirá una excepción `Clone Not Supported Exception`. Tras obtener una copia de la clase padre, el método `clone()` de la propia clase puede proporcionar capacidades de clonación personalizadas, como la copia profunda (es decir, duplicar algunas de las estructuras a las que hace referencia el objeto) o asignar a la nueva instancia un identificador único.
El tipo de retorno de clone() es Object, pero quienes implementen un método clone podrían escribir el tipo del objeto que se clona debido a la compatibilidad de Java con los tipos de retorno covariantes . Una ventaja de usar clone() es que, al ser un método anulable , podemos llamar a clone() en cualquier objeto, y este utilizará el método clone() de su clase, sin que el código que lo llama necesite saber cuál es esa clase (lo cual sería necesario con un constructor de copia).
Una desventaja es que a menudo no se puede acceder al método clone() en un tipo abstracto . La mayoría de las interfaces y clases abstractas en Java no especifican un método clone() público. Por lo tanto, con frecuencia la única forma de usar el método clone() es si se conoce la clase de un objeto, lo cual es contrario al principio de abstracción de usar el tipo más genérico posible. Por ejemplo, si se tiene una referencia a List en Java, no se puede invocar clone() en esa referencia porque List no especifica un método clone() público. Las implementaciones de List, como ArrayList y LinkedList, generalmente tienen métodos clone(), pero es inconveniente y una mala abstracción tener que lidiar con el tipo de clase de un objeto.
Otra forma de copiar objetos en Java es serializarlos mediante la Serializableinterfaz. Esto se suele usar para fines de persistencia y protocolos de comunicación , pero crea copias de objetos y, a diferencia de `clone`, permite realizar copias profundas que manejan correctamente los grafos cíclicos de objetos con un mínimo esfuerzo por parte del programador.
Ambos métodos presentan un problema importante: el constructor no se utiliza para los objetos copiados mediante clonación o serialización. Esto puede provocar errores con datos inicializados incorrectamente, impide el uso de finalcampos miembro y dificulta el mantenimiento. Algunas utilidades intentan solucionar estos problemas mediante la reflexión para realizar copias profundas de objetos, como la biblioteca deep-cloning. [ 12 ]
En Eiffel
En Eiffel, los objetos en tiempo de ejecución son accesibles indirectamente a través de referencias o como objetos expandidos cuyos campos están integrados dentro de los objetos que los utilizan. Es decir, los campos de un objeto se almacenan externa o internamente .
La clase Eiffel ANYcontiene funcionalidades para la copia y clonación superficial y profunda de objetos. Todas las clases Eiffel heredan de ANY, por lo que estas funcionalidades están disponibles en todas las clases y son aplicables tanto a objetos de referencia como a objetos expandidos.
Esta copyfunción realiza una copia superficial, campo por campo, de un objeto a otro. En este caso, no se crea ningún objeto nuevo. Si yse copiaron a x, entonces los mismos objetos a los que hacía referencia yantes de la aplicación de copy, también serán referenciados por xdespués de que la copyfunción se complete.
Para crear un nuevo objeto que sea una copia superficial de y, se utiliza la función twin. En este caso, se crea un nuevo objeto con campos idénticos a los del objeto original.
La característica twinse basa en la característica copy, que puede redefinirse en descendientes de ANY, si es necesario. El resultado de twines del tipo anclado like Current.
La copia profunda y la creación de gemelos profundos se pueden realizar utilizando las características deep_copyy deep_twin, heredadas de la clase ANY. Estas características tienen el potencial de crear muchos objetos nuevos, ya que duplican todos los objetos en una estructura de objetos completa. Debido a que se crean nuevos objetos duplicados en lugar de simplemente copiar referencias a objetos existentes, las operaciones profundas se convertirán en una fuente de problemas de rendimiento con mayor facilidad que las operaciones superficiales.
En otros idiomas
En C# , en lugar de usar la interfaz , se puede utilizar un método de extensiónICloneable genérico para crear una copia profunda mediante reflexión. Esto ofrece dos ventajas: primero, proporciona la flexibilidad de copiar cualquier objeto sin tener que especificar manualmente cada propiedad y variable que se va a copiar; segundo, dado que el tipo es genérico, el compilador garantiza que el objeto de destino y el objeto de origen tengan el mismo tipo.
En Objective-C , los métodos copyy mutableCopyson heredados por todos los objetos y están destinados a realizar copias; el último se utiliza para crear un tipo mutable del objeto original. Estos métodos, a su vez, llaman a los métodos copyWithZoney mutableCopyWithZone, respectivamente, para realizar la copia. Un objeto debe implementar el copyWithZonemétodo correspondiente para poder copiarse.
En OCaml , la función de biblioteca Oo.copy realiza una copia superficial de un objeto.
En Python , el módulo de copia de la biblioteca proporciona copia superficial y copia profunda de objetos a través de las funciones copy()y , respectivamente. [ 13 ] Los programadores pueden definir métodos especiales y en un objeto para proporcionar una implementación de copia personalizada.deepcopy()__copy__()__deepcopy__()
En Ruby , todos los objetos heredan dos métodos para realizar copias superficiales: `clone` y `dup` . La diferencia radica en que `clone` clonecopia el estado modificado, el estado congelado y cualquier método singleton que pueda tener un objeto, mientras que dup`dup` solo copia su estado modificado. Las copias profundas se pueden lograr volcando y cargando el flujo de bytes o la serialización YAML de un objeto .Como alternativa, puedes usar la gema deep_dive para realizar una copia profunda controlada de los gráficos de tus objetos.
En Rust , las estructuras pueden implementar el clonemétodo usando el Clonerasgo. [ 14 ] [ 15 ]
En Perl , las estructuras anidadas se almacenan mediante el uso de referencias, por lo que un desarrollador puede recorrer toda la estructura y volver a referenciar los datos o utilizar la dclone()función del módulo Storable .
En VBA , la asignación de variables de un tipo determinado Objectes una copia superficial, mientras que la asignación de cualquier otro tipo (numérico, cadena, tipos definidos por el usuario, matrices) es una copia profunda. Por lo tanto, la palabra clave Set`assign` indica una copia superficial y la palabra clave `adsign` (opcional) Letindica una copia profunda. No existe ningún método integrado para realizar copias profundas de objetos en VBA.
Véase también
Notas
- ^ Grogono y Sakkinen 2000 .
- ↑ Goldberg y Robson 1983 , págs. 97-99. "Hay dos maneras de hacer copias de un objeto. La diferencia radica en si se copian o no los valores de las variables del objeto. Si los valores no se copian, entonces se comparten (
shallowCopy); si los valores se copian, entonces no se comparten (deepCopy)." - ↑ "Explicación de copia superficial vs. copia profunda en C++" . Archivado del original el 10 de febrero de 2014. Consultado el 10 de abril de 2013 .
- ↑ "Explicación de copia superficial vs. copia profunda en .NET" .
- ↑ "Explicación genérica de copia superficial frente a copia profunda" . Archivado del original el 4 de marzo de 2016. Consultado el 10 de abril de 2013 .
- ↑ Java básico: Fundamentos, Volumen 1, pág. 295
- ↑ Java eficaz , segunda edición, pág. 54
- ↑ " ¿Qué es esta copia campo por campo que realiza Object.clone()? ", Stack Overflow
- ↑ "Josh Bloch sobre diseño: una conversación con el eficaz autor de Java, Josh Bloch", por Bill Venners, JavaWorld , 4 de enero de 2002, pág. 13
- ↑ Goldberg & Robson 1983 , p. 97. "La implementación predeterminada de
copyesshallowCopy. En las subclases en las que la copia debe resultar en una combinación especial de variables compartidas y no compartidas, el método asociado con copy generalmente se reimplementa, en lugar del método asociado conshallowCopyodeepCopy." - ↑ "Pasar información a un método o a un constructor" . Consultado el 8 de octubre de 2013 .
- ↑ Biblioteca de clonación profunda de Java
- ↑ Módulo de copia de Python
- ↑ "Clonar en std::clone - Rust" . doc.rust-lang.org . Consultado el 3 de noviembre de 2025 .
- ↑ "Clonar - Rust por ejemplo" . doc.rust-lang.org . Consultado el 3 de noviembre de 2025 .
Referencias
- Goldberg, Adele ; Robson, David (1983). Smalltalk-80: El lenguaje y su implementación . Palo Alto, California: Xerox Palo Alto Research Center. ISBN 978-0-201-11371-6.
- Grogono, Peter; Sakkinen, Markku (12 de mayo de 2000). "Copiar y comparar: problemas y soluciones" (PDF) . En Elisa Bertino (ed.). Lecture Notes in Computer Science . ECOOP 2000 — Programación orientada a objetos. Vol. 1850. Springer Berlin Heidelberg. pp. 226–250 . doi : 10.1007/3-540-45102-1_11 . Archivado del original (PDF) el 4 de marzo de 2016. Recuperado el 23 de junio de 2015 .
- Objeto (informática)
