Sobre Java clonável

95

Eu estava procurando alguns tutoriais explicando sobre Java Cloneable, mas não consegui nenhum link bom, e Stack Overflow está se tornando a escolha mais óbvia de qualquer maneira.

Eu gostaria de saber o seguinte:

  1. Cloneablesignifica que podemos ter um clone ou uma cópia dos objetos, implementando a Cloneableinterface. Quais são as vantagens e desvantagens de fazer isso?
  2. Como a clonagem recursiva acontece se o objeto é um objeto composto?
sonhador
fonte
2
As vantagens e desvantagens em relação a quê?
Galactus
4
Eu li que isso significa vantagem de uma classe ser clonável versus não. Não tenho certeza de como isso poderia ser interpretado: S
allyourcode

Respostas:

159

A primeira coisa que você deve saber Cloneableé - não o use.

É muito difícil implementar a clonagem com Cloneablerazão, e o esforço não vale a pena.

Em vez disso, use algumas outras opções, como apache-commons SerializationUtils(clone profundo) ou BeanUtils(clone raso), ou simplesmente use um construtor de cópia.

Veja aqui as opiniões de Josh Bloch sobre a clonagem com Cloneable, o que explica as muitas desvantagens dessa abordagem. ( Joshua Bloch era um funcionário da Sun e liderou o desenvolvimento de vários recursos Java.)

Bozho
fonte
1
Eu vinculei as palavras de Bloch (em vez de citá-las)
Bozho
3
Observe que Block diz para não usar Cloneable. Ele não diz para não usar clonagem (ou pelo menos espero que não). Existem muitas maneiras de implementar a clonagem que são muito mais eficientes do que classes como SerializationUtils ou BeanUtils que usam reflexão. Veja meu post abaixo para um exemplo.
Charles de
qual é a alternativa para um construtor de cópia ao definir interfaces? basta adicionar um método de cópia?
Benez
@benez, eu diria que sim. desde java-8, você pode ter staticmétodos em interfaces, então apenas forneça um static WhatEverTheInterface copy(WhatEverTheInterface initial)? mas eu me pergunto o que isso oferece, já que você copia campos de um objeto durante a clonagem, mas uma interface define apenas métodos. cuidado para explicar?
Eugene
40

O próprio Cloneable é, infelizmente, apenas uma interface de marcador, isto é: ele não define o método clone ().

O que isso faz é alterar o comportamento do método protegido Object.clone (), que lançará uma CloneNotSupportedException para classes que não implementam Cloneable e executará uma cópia superficial do membro para classes que o fazem.

Mesmo que esse seja o comportamento que você está procurando, você ainda precisará implementar seu próprio método clone () para torná-lo público.

Ao implementar seu próprio clone (), a ideia é começar com o objeto criado por super.clone (), que é garantido ser da classe correta, e então fazer qualquer preenchimento adicional de campos caso uma cópia superficial não seja o que você quer. Chamar um construtor de clone () seria problemático, pois isso quebraria a herança no caso de uma subclasse desejar adicionar sua própria lógica clonável adicional; se fosse chamar super.clone (), ele obteria um objeto da classe errada neste caso.

Essa abordagem ignora qualquer lógica que possa ser definida em seus construtores, o que pode ser potencialmente problemático.

Outro problema é que quaisquer subclasses que esquecerem de substituir clone () herdarão automaticamente a cópia superficial padrão, o que provavelmente não é o que você deseja no caso de estado mutável (que agora será compartilhado entre a fonte e a cópia).

A maioria dos desenvolvedores não usa o Cloneable por esses motivos e simplesmente implementa um construtor de cópia.

Para obter mais informações e possíveis armadilhas de Cloneable, recomendo o livro Effective Java de Joshua Bloch

Luke Hutteman
fonte
12
  1. A clonagem invoca uma forma extralinguística de construir objetos - sem construtores.
  2. A clonagem requer que você trate de alguma forma com CloneNotSupportedException - ou incomode o código do cliente para tratá-lo.
  3. Os benefícios são pequenos - você simplesmente não precisa escrever manualmente um construtor de cópia.

Portanto, use o Cloneable criteriosamente. Não oferece benefícios suficientes em comparação com o esforço que você precisa aplicar para fazer tudo certo.

Vladimir Ivanov
fonte
Como Bozho disse, não use Cloneable. Em vez disso, use um construtor de cópia. javapractices.com/topic/TopicAction.do?Id=12
Bane
@Bane, e se você não souber o tipo de objeto a ser clonado, como saberá qual construtor de cópia de classe invocar?
Steve Kuo
@Steve: Eu não entendo. Se você for clonar um objeto, presumo que já saiba de que tipo ele é - afinal, você tem o objeto em mãos que planeja clonar. E se houver uma situação em que seu objeto perdeu seu tipo específico para um mais genérico, você não poderia avaliá-lo usando uma simples 'instância de' ???
Bane
4
@Bane: Suponha que você tenha uma lista de objetos todos derivados do tipo A, talvez com 10 tipos diferentes. Você não sabe qual é o tipo de cada objeto. Usar instanceof neste caso é uma ideia MUITO ruim. Se você adicionar outro tipo, sempre que fizer isso, terá que adicionar outra instância de teste. E se as classes derivadas estiverem em outro pacote que você nem consegue acessar? A clonagem é um padrão comum. Sim, a implementação do java é ruim, mas existem muitas maneiras de contornar isso que funcionam bem. Um construtor de cópia não é uma operação equivalente.
Charles de
@Charles: Na ausência de um exemplo detalhado e na falta de experiência recente em lidar com esse tipo de problema, terei que passar para Bloch. Item nº 11. É longo e um pouco difícil de ler, mas basicamente diz "evite clonável sempre que puder, os construtores de cópia são seus amigos".
Bane
7

A clonagem é um paradigma de programação básico. O fato de que o Java pode ter implementado mal em muitos aspectos não diminui a necessidade de clonagem. E é fácil implementar a clonagem que funcionará da maneira que você quiser, superficial, profunda, mista, o que for. Você pode até usar o nome clone para a função e não implementar Cloneable, se desejar.

Suponha que eu tenha classes A, B e C, onde B e C são derivados de A. Se eu tiver uma lista de objetos do tipo A como esta:

ArrayList<A> list1;

Agora, essa lista pode conter objetos do tipo A, B ou C. Você não sabe de que tipo são os objetos. Portanto, você não pode copiar a lista desta forma:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

Se o objeto for realmente do tipo B ou C, você não obterá a cópia correta. E se A for abstrato? Agora, algumas pessoas sugeriram isto:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

Esta é uma ideia muito, muito ruim. E se você adicionar um novo tipo derivado? E se B ou C estiverem em outro pacote e você não tiver acesso a eles nesta aula?

O que você gostaria de fazer é:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

Muitas pessoas indicaram por que a implementação básica do Java do clone é problemática. Mas, é facilmente superado desta forma:

Na classe A:

public A clone() {
    return new A(this);
}

Na classe B:

@Override
public B clone() {
    return new B(this);
}

Na classe C:

@Override
public C clone() {
    return new C(this):
}

Não estou implementando Cloneable, apenas usando o mesmo nome de função. Se você não gosta disso, dê outro nome.

Charles
fonte
Acabei de ver isso depois de responder ao seu comentário em uma resposta separada; Eu vejo onde você está indo agora, no entanto, 2 coisas: 1) o OP perguntou especificamente sobre o uso de Cloneable (não sobre o conceito genérico de clonagem) e 2) você está perdendo a cabeça aqui ao tentar distinguir entre um construtor de cópia e o conceito genérico de clonagem. A ideia que você transmite aqui é válida, mas na raiz você está apenas usando um construtor de cópia. ;)
Bane
Embora eu queira dizer que concordo com sua abordagem aqui, a de incluir um A # copyMethod (), em vez de forçar o usuário a chamar o construtor de cópia diretamente.
Bane
5

A) Não há muitas vantagens do clone sobre um construtor de cópia. Provavelmente, o maior deles é a capacidade de criar um novo objeto exatamente do mesmo tipo dinâmico (assumindo que o tipo declarado é clonável e tem um método clone público).

B) O clone padrão cria uma cópia superficial e permanecerá uma cópia superficial, a menos que sua implementação de clone mude isso. Isso pode ser difícil, especialmente se sua classe tiver campos finais

Bozho está certo, clonar pode ser difícil de acertar. Um construtor / fábrica de cópias atenderá à maioria das necessidades.

ILMTitan
fonte
0

Quais são as desvantagens do clonável?

A clonagem é muito perigosa se o objeto que você está copiando tem composição. Você precisa pensar sobre o possível efeito colateral neste caso, porque o clone cria uma cópia superficial:

Digamos que você tenha um objeto para lidar com a manipulação relacionada ao banco de dados. Diga, esse objeto temConnection objeto como uma das propriedades.

Então, quando alguém cria um clone de originalObject, O objeto sendo criado, digamos cloneObject,. Aqui, o originalObjecte cloneObjectmantém a mesma referência paraConnection objeto.

Digamos que originalObjectfeche o Connectionobjeto, então agora o cloneObjectnão funcionará porque o connectionobjeto foi compartilhado entre eles e na verdade foi fechado pelooriginalObject .

Problema semelhante pode ocorrer se você quiser clonar um objeto que tem IOStream como uma propriedade.

Como a clonagem recursiva acontece se o objeto é um objeto composto?

A clonagem executa uma cópia superficial. O significado é que os dados do objeto original e do objeto clone apontarão para a mesma referência / memória. ao contrário, no caso de cópia profunda, os dados da memória do objeto original são copiados para a memória do objeto clone.

027
fonte
Seu último parágrafo é muito confuso. Cloneablenão executa uma cópia, Object.clonefaz. "Os dados da memória do objeto original são copiados para a memória do objeto clone" é exatamente o Object.cloneque o faz. Você precisa falar sobre a memória de objetos referenciados para descrever a cópia profunda.
aioobe