Question

Je dois mettre en œuvre un clone profond dans l'un de mes objets qui n'a pas de superclasse.

Quelle est la meilleure façon de gérer la CloneNotSupportedException vérifiée lancée par la superclasse (qui est Object)?

Un collègue de travail m'a conseillé de le manipuler de la façon 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 semble être une bonne solution pour moi, mais je voulais le jeter à la communauté StackOverflow pour voir s'il y a d'autres idées que je peux comprendre. Merci!

Était-ce utile?

La solution

Avez-vous absolument utiliser clone? La plupart des gens conviennent que le clone Java est cassé.

Josh Bloch sur la conception - Copie contre Constructor 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 clone est profondément brisée. [...] Il est dommage que Cloneable est cassé, mais il arrive.

Vous pouvez lire une discussion plus approfondie sur le sujet dans son livre Effective Java 2e édition, point 11: override clone judicieusement . Il recommande plutôt d'utiliser une usine de constructeur ou une copie copie.

Il a continué à écrire des pages de pages sur la façon, si vous sentez que vous devez, vous devez mettre en œuvre clone. Mais il a fermé avec ceci:

  

Tout cela est vraiment nécessaire complexité? Rarement. Si vous étendez une classe qui implémente Cloneable, vous avez peu de choix mais de mettre en œuvre une méthode de clone bien comportés. Dans le cas contraire, vous êtes mieux fournir des moyens alternatifs de copie d'objet, ou tout simplement pas fournir la capacité .

L'accent était le sien, pas le mien.


Puisque vous fait comprendre que vous avez peu de choix mais 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 tel est le cas, alors vous pouvez garantir que vous 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 catch ne sera jamais entré dans ce cas particulier .


Sinon, comme d'autres ont également suggéré, vous pouvez peut-être mettre en œuvre clone sans appeler super.clone.

Autres conseils

Parfois, il est plus simple à mettre en œuvre un constructeur de copie:

public MyObject (MyObject toClone) {
}

Il vous évite de traiter CloneNotSupportedException, travaille avec des champs de final et vous n'avez pas à vous soucier du type de retour.

La façon dont fonctionne votre code est assez proche de la manière « canonique » pour l'écrire. Je jette un AssertionError dans le hic cependant. Il signale que cette ligne ne doit jamais être atteint.

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

Il y a deux cas où le CloneNotSupportedException sera lancé:

  1. La classe cloné ne Cloneable pas mis en œuvre (en supposant que le clonage réel diffère finalement à la méthode clone de Object). Si la classe que vous écrivez cette méthode dans les outils Cloneable, cela ne se produira jamais (puisque toutes les sous-classes hériteront de façon appropriée).
  2. L'exception est levée explicitement par une mise en œuvre - c'est la méthode recommandée pour éviter clonabilité dans une sous-classe lorsque la superclasse est Cloneable
  3. .

Dans ce dernier cas ne peut se produire dans votre classe (comme vous appelez directement la méthode de superclasse dans le bloc try, même si invoqué à partir d'une sous-classe appelant super.clone()) et l'ancien ne devrait pas depuis votre classe devrait mettre en œuvre Cloneable clairement.

En gros, vous devez enregistrer l'erreur pour sûr, mais dans ce cas particulier, il ne se produira que si vous gâcher votre classe définition. Ainsi traiter comme une version vérifiée de NullPointerException (ou similaire) -. il ne sera jamais levée si votre code est fonctionnel


Dans d'autres situations, vous devez être prêt à cette éventualité - il n'y a aucune garantie qu'un objet donné est cloneable, donc quand attraper l'exception, vous devez prendre les mesures appropriées en fonction de cette condition (continuer avec l'objet existant, prendre une stratégie de clonage alternatif, par exemple serialize-deserialize, jetez un IllegalParameterException si votre méthode nécessite le paramètre par cloneable, etc., etc.).

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

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

Vous pouvez mettre en œuvre des constructeurs protégés contre la copie comme ceci:

/* This is a protected copy constructor for exclusive use by .clone() */
protected MyObject(MyObject that) {
    this.myFirstMember = that.getMyFirstMember(); //To clone primitive data
    this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects
    // etc
}

public MyObject clone() {
    return new MyObject(this);
}
public class MyObject implements Cloneable, Serializable{   

    @Override
    @SuppressWarnings(value = "unchecked")
    protected MyObject clone(){
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            ByteArrayOutputStream bOs = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bOs);
            oos.writeObject(this);
            ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
            return  (MyObject)ois.readObject();

        } catch (Exception e) {
            //Some seriouse error :< //
            return null;
        }finally {
            if (oos != null)
                try {
                    oos.close();
                } catch (IOException e) {

                }
            if (ois != null)
                try {
                    ois.close();
                } catch (IOException e) {

                }
        }
    }
}

Autant que la plupart des réponses ici sont valables, je dois dire que votre solution est aussi comment les développeurs de l'API Java réels font. (Soit Josh Bloch ou Neal Gafter)

Voici un extrait de OpenJDK, classe ArrayList:

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

Comme vous l'avez remarqué et d'autres mentionnés, CloneNotSupportedException n'a pratiquement aucune chance d'être jeté si vous déclarez que vous implémenter l'interface Cloneable.

En outre, il n'y a pas besoin pour vous de remplacer la méthode si vous ne faites pas quelque chose de nouveau dans la méthode surchargée. Il vous suffit de la remplacer lorsque vous devez effectuer des opérations supplémentaires sur l'objet ou vous devez le rendre public.

En fin de compte, il est toujours préférable d'éviter et de le faire en utilisant une autre manière.

Tout simplement parce que la mise en œuvre de Java de Cloneable est cassé, il ne signifie pas que vous ne pouvez pas créer un de vos propres.

Si vrai but OP était de créer un clone profond, je pense qu'il est possible de créer une interface comme ceci:

public interface Cloneable<T> {
    public T getClone();
}

puis utilisez le constructeur de prototype mentionné avant de l'appliquer:

public class AClass implements Cloneable<AClass> {
    private int value;
    public AClass(int value) {
        this.vaue = value;
    }

    protected AClass(AClass p) {
        this(p.getValue());
    }

    public int getValue() {
        return value;
    }

    public AClass getClone() {
         return new AClass(this);
    }
}

et une autre classe avec un champ d'objet AClass:

public class BClass implements Cloneable<BClass> {
    private int value;
    private AClass a;

    public BClass(int value, AClass a) {
         this.value = value;
         this.a = a;
    }

    protected BClass(BClass p) {
        this(p.getValue(), p.getA().getClone());
    }

    public int getValue() {
        return value;
    }

    public AClass getA() {
        return a;
    }

    public BClass getClone() {
         return new BClass(this);
    }
}

De cette façon, vous pouvez facilment clone profond un objet de BClass de classe sans avoir besoin de @SuppressWarnings ou tout autre code gimmicky.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top