Tipos por referência e cópia de objetos em Java
Em Java, todas as instâncias de classes são tipos por referência. Isso significa que uma variável de referência não armazena diretamente o objeto, mas sim um endereço de memória que aponta para ele. Esse comportamento é fundamental para entender como os objetos se comportam ao serem atribuídos a outras variáveis.
Considere o exemplo:
public class Program {
public static void main(String[] args) {
Person tom = new Person("Tom", 23);
tom.display(); // Person Tom
Person bob = tom;
bob.setName("Bob");
tom.display(); // Person Bob
}
}
class Person {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
void setName(String name) {
this.name = name;
}
void setAge(int age) {
this.age = age;
}
void display() {
System.out.printf("Person Name: %s \n", name);
}
}
Nesse código, bob
e tom
acabam apontando para o mesmo objeto na memória. Alterar o nome usando bob.setName("Bob")
também altera o valor visualizado por tom
.
Clonando objetos para evitar referências compartilhadas
Para criar um novo objeto com os mesmos valores, é necessário clonar a instância original. O Java fornece suporte para isso por meio do método clone()
e da interface Cloneable
.
class Person implements Cloneable {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
void setName(String name) {
this.name = name;
}
void setAge(int age) {
this.age = age;
}
void display() {
System.out.printf("Person %s \n", name);
}
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
A interface Cloneable
indica que a classe permite clonagem. O método clone()
chama super.clone()
(implementação padrão de Object
), que cria uma cópia superficial do objeto.
Uso:
try {
Person tom = new Person("Tom", 23);
Person bob = tom.clone();
bob.setName("Bob");
tom.display(); // Person Tom
} catch (CloneNotSupportedException ex) {
System.out.println("Cloneable not implemented");
}
Cópia superficial e seus limites
A cópia superficial (shallow copy) copia apenas os valores dos campos primitivos e as referências para objetos internos. Isso significa que objetos aninhados continuam sendo compartilhados entre o original e o clone.
Exemplo:
class Book implements Cloneable {
private String name;
private Author author;
public void setName(String n) { name = n; }
public String getName() { return name; }
public void setAuthor(String n) { author.setName(n); }
public String getAuthor() { return author.getName(); }
Book(String name, String author) {
this.name = name;
this.author = new Author(author);
}
public String toString() {
return "Livro '" + name + "' (autor " + author + ")";
}
public Book clone() throws CloneNotSupportedException {
return (Book) super.clone();
}
}
class Author {
private String name;
public void setName(String n) { name = n; }
public String getName() { return name; }
public Author(String name) {
this.name = name;
}
}
Se clonarmos e alterarmos o autor no clone, o livro original também será afetado:
try {
Book book = new Book("War and Peace", "Leo Tolstoy");
Book book2 = book.clone();
book2.setAuthor("Ivan Turgenev");
System.out.println(book.getAuthor()); // Ivan Turgenev
} catch (CloneNotSupportedException ex) {
System.out.println("Cloneable not implemented");
}
Isso ocorre porque o campo author
é apenas uma referência compartilhada.
Cópia profunda para independência total
Para evitar esse problema, é necessário implementar uma cópia profunda (deep copy), criando novas instâncias para os objetos internos.
O primeiro passo é tornar Author
também clonável:
class Author implements Cloneable {
private String name;
public void setName(String n) { name = n; }
public String getName() { return name; }
public Author(String name) {
this.name = name;
}
public Author clone() throws CloneNotSupportedException {
return (Author) super.clone();
}
}
Agora, modificamos o método clone()
de Book
para clonar também o autor:
public Book clone() throws CloneNotSupportedException {
Book newBook = (Book) super.clone();
newBook.author = (Author) author.clone();
return newBook;
}
Com essa alteração, book
e book2
terão autores totalmente independentes, garantindo que mudanças em um não afetem o outro.
Resumo
- Objetos em Java são armazenados como referências, e variáveis guardam o endereço de memória.
- Ao atribuir uma variável de referência a outra, ambas passam a apontar para o mesmo objeto.
- O método
clone()
cria uma nova instância copiando os valores do objeto original. - A cópia superficial (shallow copy) copia apenas referências internas, fazendo com que objetos internos sejam compartilhados.
- A cópia profunda (deep copy) cria novas instâncias para todos os objetos internos, garantindo independência total.