125 votes

Comment remplacer correctement la méthode clone ?

J'ai besoin d'implémenter un clone profond dans un de mes objets qui n'a pas de superclasse.

Quelle est la meilleure façon de gérer le contrôle CloneNotSupportedException lancées par la superclasse (qui est Object ) ?

Un collègue de travail m'a conseillé d'agir de la manière suivante :

@Override
public MyObject clone()
{
    MyObject foo;
    try
    {
        foo = (MyObject) super.clone();
    }
    catch (CloneNotSupportedException e)
    {
        throw new Error();
    }

    // Deep clone member fields here

    return foo;
}

Cela me semble être une bonne solution, mais je voulais la soumettre à la communauté StackOverflow pour voir s'il y a d'autres idées que je peux inclure. Merci !

6 votes

Si vous savez que la classe parent implémente Cloneable puis en lançant un AssertionError plutôt qu'un simple Error est un peu plus expressif.

0 votes

Edit : Je préférerais ne pas utiliser clone(), mais le projet est déjà basé dessus et ça ne vaudrait pas la peine de refactorer toutes les références à ce stade.

0 votes

Il est préférable de s'y atteler maintenant plutôt que plus tard (à moins que vous ne soyez sur le point d'entrer en production).

131voto

polygenelubricants Points 136838

Devez-vous absolument utiliser clone ? La plupart des gens s'accordent à dire que le clone est cassé.

Josh Bloch on Design - Constructeur de copie ou clonage ?

Si vous avez lu l'article sur le clonage dans mon livre, surtout si vous lisez entre les lignes, vous saurez que je pense que clone est profondément brisé. [...] C'est une honte que... Cloneable est cassé, mais ça arrive.

Vous pouvez lire plus d'informations sur le sujet dans son livre. Effective Java 2nd Edition, Item 11 : Override clone judicieusement . Il recommande plutôt d'utiliser un constructeur de copie ou une fabrique de copie.

Il a poursuivi en écrivant des pages et des pages sur la façon dont, si vous vous sentez obligé, vous devriez mettre en œuvre les mesures suivantes clone . Mais il a terminé par ceci :

Toutes ces complexités sont-elles vraiment nécessaires ? Rarement. Si vous étendez une classe qui implémente Cloneable vous n'avez pas d'autre choix que de mettre en place un système de gestion de l'information bien conçu clone méthode. Sinon, il est préférable de fournir des moyens alternatifs de copie d'objets, ou simplement de ne pas fournir cette capacité. .

C'est lui qui a mis l'accent, pas moi.


Puisque vous avez clairement indiqué que vous n'avez pas d'autre choix que de mettre en œuvre clone voici ce que vous pouvez faire dans ce cas : assurez-vous que MyObject extends java.lang.Object implements java.lang.Cloneable . Si c'est le cas, alors vous pouvez garantir que vous allez JAMAIS attraper un CloneNotSupportedException . Lancer AssertionError comme certains l'ont suggéré semble raisonnable, mais vous pouvez également ajouter un commentaire qui explique pourquoi le bloc de capture ne sera jamais saisi dans ce cas particulier .


Alternativement, comme d'autres l'ont suggéré, vous pouvez peut-être implémenter clone sans appeler super.clone .

2 votes

Malheureusement, le projet est déjà écrit autour de l'utilisation de la méthode clone, sinon je ne l'utiliserais absolument pas. Je suis tout à fait d'accord avec vous pour dire que l'implémentation de clone par Java est fakakta.

7 votes

Si une classe et toutes ses superclasses appellent super.clone() dans leurs méthodes de clonage, une sous-classe n'aura généralement qu'à remplacer clone() s'il ajoute de nouveaux champs dont le contenu devrait être cloné. Si une superclasse utilise new plutôt que super.clone() alors toutes les sous-classes doivent remplacer clone() qu'ils ajoutent ou non de nouveaux champs.

61voto

Aaron Digulla Points 143830

Parfois, il est plus simple d'implémenter un constructeur de copie :

public MyObject (MyObject toClone) {
}

Cela vous évite de devoir gérer CloneNotSupportedException , travaille avec final et vous n'avez pas à vous soucier du type à retourner.

13voto

Chris Jester-Young Points 102876

La façon dont votre code fonctionne est assez proche de la façon "canonique" de l'écrire. J'ajouterais un AssertionError dans la prise, cependant. Il signale que cette ligne ne doit jamais être atteinte.

catch (CloneNotSupportedException e) {
    throw new AssertionError(e);
}

0 votes

Voir la réponse de @polygenelubricants pour savoir pourquoi cela pourrait être une mauvaise idée.

1 votes

@KarlRichter J'ai lu leur réponse. Elle ne dit pas que c'est une mauvaise idée, à part cela. Cloneable en général est une idée erronée, comme l'explique Effective Java. Le PO a déjà exprimé qu'il doit utiliser Cloneable . Je n'ai donc aucune idée de la manière dont ma réponse pourrait être améliorée, sauf peut-être en la supprimant purement et simplement.

9voto

Andrzej Doyle Points 52541

Il existe deux cas dans lesquels le CloneNotSupportedException seront lancées :

  1. La classe clonée n'est pas implémentée Cloneable (en supposant que le clonage réel s'en remette finalement à Object (méthode du clone). Si la classe dans laquelle vous écrivez cette méthode implémente la méthode Cloneable cela ne se produira jamais (puisque toutes les sous-classes en hériteront de manière appropriée).
  2. L'exception est explicitement levée par une implémentation - c'est la façon recommandée d'empêcher la clonabilité dans une sous-classe lorsque la superclasse est Cloneable .

Ce dernier cas ne peut pas se produire dans votre classe (puisque vous appelez directement la méthode de la superclasse dans la fonction try même s'il est invoqué à partir d'une sous-classe appelant le bloc super.clone() ) et le premier ne devrait pas l'être puisque votre classe devrait clairement implémenter Cloneable .

En fait, vous devriez enregistrer l'erreur, mais dans ce cas particulier, cela ne se produira que si vous vous trompez dans la définition de votre classe. Ainsi, traitez-le comme une version vérifiée de NullPointerException (ou similaire) - elle ne sera jamais lancée si votre code est fonctionnel.


Dans d'autres situations, il faudrait se préparer à cette éventualité - il n'y a aucune garantie qu'un objet donné es clonable, de sorte que lorsque vous récupérez l'exception, vous devez prendre les mesures appropriées en fonction de cette condition (continuer avec l'objet existant, adopter une autre stratégie de clonage, par exemple sérialiser-désérialiser, lancer un message d'erreur de type IllegalParameterException si votre méthode exige que le paramètre soit clonable, etc. etc.).

Modifier : Bien que dans l'ensemble, je dois préciser que oui, clone() est vraiment difficile à mettre en œuvre correctement et difficile pour les appelants de savoir si la valeur de retour sera ce qu'ils veulent, doublement si l'on considère les clones profonds par rapport aux clones superficiels. Il est souvent préférable d'éviter tout cela et d'utiliser un autre mécanisme.

0 votes

Si un objet expose une méthode de clonage publique, tout objet dérivé qui ne la prendrait pas en charge violerait le principe de substitution de Liskov. Si une méthode de clonage est protégée, je pense qu'il serait préférable de la masquer avec quelque chose d'autre qu'une méthode retournant le type approprié, afin d'empêcher une sous-classe d'essayer d'appeler super.clone() .

6voto

Michał Ziober Points 5787

Utilisez sérialisation pour faire des copies profondes. Ce n'est pas la solution la plus rapide mais elle ne dépend pas du type.

0 votes

C'était une solution excellente et évidente, étant donné que j'utilisais déjà le GSON.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X