In questo articolo del mio corso gratuito Java 8, discuterò la differenza tra una copia profonda e una copia superficiale. È possibile scaricare le diapositive e l’articolo in formato PDF qui.
Che cos’è una copia?
Per iniziare, vorrei evidenziare cos’è una copia in Java. Innanzitutto, distinguiamo tra una copia di riferimento e una copia di un oggetto. Una copia di riferimento, come suggerisce il nome, crea una copia di una variabile di riferimento che punta a un oggetto., Se abbiamo un oggetto Car, con una variabile myCar che punta ad esso e facciamo una copia di riferimento, ora avremo due variabili myCar, ma ancora un oggetto.
Esempio 1
Una copia dell’oggetto crea una copia dell’oggetto stesso. Quindi, se copiassimo di nuovo il nostro oggetto car, creeremmo una copia dell’oggetto stesso, così come una seconda variabile di riferimento che fa riferimento all’oggetto copiato.
Esempio 2
Che cos’è un oggetto?,
Sia una copia profonda che una copia superficiale sono tipi di copie di oggetti, ma cos’è realmente un oggetto? Spesso, quando parliamo di un oggetto, lo parliamo come una singola unità che non può essere scomposta ulteriormente, come un umile chicco di caffè. Tuttavia, questo è semplicissimo.
Esempio 3
Supponiamo di avere un oggetto Person. Il nostro oggetto Persona è infatti composto da altri oggetti, come potete vedere nell’esempio 4. La nostra persona contiene un oggetto Name e un oggetto Address., Il Nome a sua volta, contiene un FirstName e un oggetto LastName; l’oggetto Address è composto da un oggetto Street e un oggetto City. Quindi, quando parlo di Persona in questo articolo, in realtà sto parlando di questa intera rete di oggetti.
Esempio 4
Quindi perché dovremmo voler copiare questo oggetto Person? Una copia dell’oggetto, di solito chiamata clone, viene creata se vogliamo modificare o spostare un oggetto, pur mantenendo l’oggetto originale. Esistono molti modi diversi per copiare un oggetto che puoi conoscere in un altro articolo., In questo articolo utilizzeremo specificamente un costruttore di copie per creare le nostre copie.
Copia superficiale
Prima parliamo della copia superficiale. Una copia superficiale di un oggetto copia l’oggetto ‘principale’, ma non copia gli oggetti interni. Gli “oggetti interni” sono condivisi tra l’oggetto originale e la sua copia. Ad esempio, nel nostro oggetto Persona, creeremmo una seconda persona, ma entrambi gli oggetti condividerebbero gli stessi oggetti Nome e indirizzo.
Diamo un’occhiata a un esempio di codifica. Nell’esempio 5, abbiamo la nostra persona di classe, che contiene un oggetto Nome e indirizzo., Il costruttore di copia prende l’oggetto originalPerson e copia le sue variabili di riferimento.
Esempio 5
Il problema con la copia superficiale è che i due oggetti non sono indipendenti. Se si modifica l’oggetto Nome di una Persona, la modifica si rifletterà nell’oggetto altra persona.
Applichiamo questo ad un esempio. Diciamo che abbiamo un oggetto Persona con una variabile di riferimento madre; quindi, facciamo una copia di madre, creando un oggetto di seconda persona, figlio. Se più avanti nel codice, il figlio tenta di moveOut () modificando il suo oggetto Address, la madre si muove con lui!,
Person mother = new Person(new Name(…), new Address(…));Person son = new Person(mother);son.moveOut(new Street(…), new City(…));
Esempio 6
Ciò si verifica perché i nostri oggetti madre e figlio condividono lo stesso oggetto Indirizzo, come puoi vedere illustrato nell’esempio 7. Quando cambiamo l’indirizzo in un oggetto, cambia in entrambi!
Esempio 7
Copia profonda
A differenza della copia superficiale, una copia profonda è una copia completamente indipendente di un oggetto. Se copiassimo il nostro oggetto Person, copieremmo l’intera struttura dell’oggetto.,
Esempio 8
Un cambiamento nell’oggetto Address di una persona non si rifletterebbe nell’altro oggetto come si può vedere dal diagramma nell’esempio 8. Se diamo un’occhiata al codice nell’esempio 9, puoi vedere che non stiamo solo usando un costruttore di copia sul nostro oggetto Person, ma stiamo anche utilizzando i costruttori di copia sugli oggetti interni.
Esempio 9
Usando questa copia profonda, possiamo riprovare l’esempio madre-figlio dall’esempio 6. Ora il figlio è in grado di uscire con successo!,
Tuttavia, non è la fine della storia. Per creare una vera copia profonda, dobbiamo continuare a copiare tutti gli elementi nidificati dell’oggetto Person, finché non rimangono solo tipi primitivi e “Immutabili”. Diamo un’occhiata alla classe Street per illustrare meglio questo:
Esempio 10
L’oggetto Street è composto da due variabili di istanza: String name e int number. int number è un valore primitivo e non un oggetto. È solo un valore semplice che non può essere condiviso, quindi creando una variabile di seconda istanza, stiamo creando automaticamente una copia indipendente., La stringa è immutabile. In breve, un Immutabile è un Oggetto, che, una volta creato, non può mai essere cambiato di nuovo. Pertanto, è possibile condividere senza dover creare una copia profonda di esso.
Conclusione
Per concludere, mi piacerebbe parlare di alcune tecniche di codifica che abbiamo usato nel nostro esempio madre-figlio. Solo perché una copia profonda ti consente di modificare i dettagli interni di un oggetto, come l’oggetto Address, non significa che dovresti., In questo modo diminuirebbe la qualità del codice, in quanto renderebbe la classe Person più fragile alle modifiche – ogni volta che la classe Address viene modificata, dovrai (potenzialmente) applicare modifiche anche alla classe Person. Ad esempio, se la classe Address non contiene più un oggetto Street, dovremmo cambiare il metodo moveOut() nella classe Person in aggiunta alle modifiche già apportate alla classe Address.
Nell’esempio 6 di questo articolo ho scelto solo di utilizzare un nuovo oggetto Street e City per illustrare meglio la differenza tra una copia superficiale e una profonda., Invece, consiglierei di assegnare un nuovo oggetto Address, convertendo efficacemente in un ibrido di una copia superficiale e una profonda, come puoi vedere nell’esempio 10:
Person mother = new Person(new Name(…), new Address(…));Person son = new Person(mother);son.moveOut(new Address(...));
Esempio 11
In termini orientati agli oggetti, questo viola l’incapsulamento e quindi dovrebbe essere evitato. L’incapsulamento è uno degli aspetti più importanti della programmazione orientata agli oggetti. In questo caso, avevo violato l’incapsulamento accedendo ai dettagli interni dell’oggetto Address nella nostra classe Person., Questo danneggia il nostro codice perché ora abbiamo impigliato la classe Person nella classe Address e se apportiamo modifiche alla classe Address lungo la linea, potrebbe danneggiare la classe Person come ho spiegato sopra. Mentre è ovviamente necessario interconnettere le varie classi per avere un progetto di codifica, ogni volta che si collegano due classi, è necessario analizzare i costi e i benefici.