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.