Java eficaz.Interface clonável
-
11-12-2019 - |
Pergunta
Eu li o livro Effective Java e não entendo um parágrafo onde é explicada a interface Clonable.Alguém pode me explicar este parágrafo:
...os programadores assumem que se estenderem uma classe e invocarem
super.clone
Da subclasse, o objeto retornado será uma instância da subclasse.A única maneira de uma superclasse fornecer essa funcionalidade é retornar um objeto obtido chamandosuper.clone
.Se um método clone retornar um objeto criado por um construtor, ele terá a classe errada.
Obrigado.
Solução
Devo observar para começar com isso clone
por si só está quebrado, e que um construtor de cópia, como Sheep(Sheep cloneMe)
é uma expressão muito mais elegante do que clone
, considerando o Cloneable
contrato é muito fraco.Você provavelmente já sabe disso, pois está lendo o livro, mas vale a pena colocar aqui.
De qualquer forma, para responder à pergunta:
Object.clone()
criará um objeto do mesmo tipo do objeto em que foi chamado.Por esta razão, é fortemente encorajado a "cascata" até Object
para obter o resultado que você planeja retornar.Se alguém decidir não seguir esta convenção, você acabará com um objeto do tipo da classe que quebrou a convenção, o que causará uma infinidade de problemas.
Para ilustrar eu tenho uma aula assim
class Sheep implements Cloneable {
Sheep(String name)...
public Object clone() {
return new Sheep(this.name); // bad, doesn't cascade up to Object
}
}
class WoolySheep extends Sheep {
public Object clone() {
return super.clone();
}
}
De repente, se eu fizer
WoolySheep dolly = new WoolySheep("Dolly");
WoolySheep clone = (WoolySheep)(dolly.clone()); // error
Vou receber uma exceção porque o que recebo de volta dolly.clone()
é um Sheep
, não um WoolySheep
.
Outras dicas
Não concordo com a resposta de @corsiKa.desde Java5.0.Java suporta o tipo de retorno covariante, portanto, a implementação correta para clone() deve ser:
class Sheep implements Cloneable {
Sheep(String name)...
public Sheep clone() {
return new Sheep(this.name);
}
}
class WoolySheep extends Sheep {
public WoolySheep clone() {
return super.clone(); // compile time error, Type miss match.
}
}
Além disso, o construtor de cópia alternativo sugerido não suporta polimorfismo.considere o seguinte exemplo (que o construtor de cópia não pode fazer):
interface Animal implements Cloneable {
String whatAreYou()
}
class Cat implements Animal {
String whatAreYou() {
return "I am a cat";
}
Cat clone() {
return new Cat();
}
}
class Dog implements Animal{
String whatAreYou() {
return "I am a dog";
}
Dog clone() {
return new Dog();
}
}
class Lib {
Animal cloneAnimal(Animal animal) {
return animal.clone();
}
}
class A {
protected Object clone() {
return new A();
}
}
class B extends A implements Cloneable {
public Object clone() {
return super.clone();
}
}
Aqui, A
tem uma implementação inválida de clone
porque isso lançará uma exceção:
B obj = (B)(new B()).clone();
Em vez de, A.clone()
deve ligar super.clone()
em vez de um construtor. Object.clone()
gerará então um novo objeto do tipo de tempo de execução em vez do tipo de tempo de compilação.
Todos os campos são então clonados neste novo objeto.Seria tentador usar um construtor se você já tiver um que inicialize todos os seus campos (como um construtor de cópia), mas isso resultará em comportamento incorreto para qualquer subclasse.
Se a aula for final
, então não importa, pois não pode ter nenhuma subclasse.