Un avantage du langage Java est qu'il annule le concept de pointeurs, mais il conduit également de nombreux programmeurs à ignorer souvent la différence entre les objets et les références dans la programmation. Cet article essaiera de clarifier ce concept. Et parce que Java ne peut pas résoudre le problème de la copie d'objets via une affectation simple, pendant le processus de développement, il est souvent nécessaire d'utiliser la méthode clone () pour copier des objets. Cet article vous permettra de comprendre ce que sont les clones Shadow et Deep Clone et comprennent leurs différences, avantages et inconvénients.
Êtes-vous un peu confus lorsque vous voyez ce titre: la langue java indique clairement que les pointeurs sont annulés, car les pointeurs sont souvent pratiques et aussi la cause profonde de l'insécurité du code, et cela rend également le programme très complexe et difficile à comprendre. Le code écrit par Abuse of Pointers n'est rien de moins que l'utilisation de la déclaration "Goto" déjà tristement célèbre. Il est absolument sage pour Java d'abandonner le concept de pointeurs. Mais c'est juste qu'il n'y a pas de définition claire du pointeur dans la langue java. Essentiellement, chaque nouvelle instruction renvoie une référence de pointeur. Cependant, dans la plupart des cas, Java n'a pas besoin de s'inquiéter de la façon d'opérer ce "pointeur", et encore moins d'avoir aussi effrayé que lors de l'utilisation de pointeurs en C ++. La seule chose à soucié est de passer des objets aux fonctions.
package com.zoer.src; classe publique objref {obj aobj = new obj (); int Aint = 11; public void ChangeObj (obj inobj) {inobj.str = "Valeur modifiée"; } public void ChangePri (int inInt) {inInt = 22; } public static void main (string [] args) {objref oref = new objref (); System.out.println ("avant l'appel changeObj ():" + oref.aobj); Oref.ChangeoBj (Oref.Aobj); System.out.println ("After Call ChangeObj () Méthode:" + oref.aobj); System.out.println ("========================================================================================. ====================================================================================================================================. ====================================================================================================================================. ====================================================================================================================================. Call changepri () Méthode: "+ Oref.aint); }} package com.zoer.src; classe publique obj {string str = "init value"; public String toString () {return Str; }} La partie principale de ce code appelle deux méthodes très similaires, ChangeObj () et ChangePri (). La seule différence est que l'un prend l'objet comme paramètre d'entrée, et l'autre prend le type de base int dans Java comme paramètre d'entrée. Et les paramètres d'entrée sont modifiés à l'intérieur des deux corps de fonction. La même méthode apparemment, mais les résultats de sortie du programme sont différents. La méthode ChangeObj () modifie vraiment les paramètres d'entrée, tandis que la méthode ChangePri () ne modifie pas les paramètres d'entrée.
À partir de cet exemple, Java traite différemment les objets et les types de données de base. Comme C, lorsque les types de données de base de Java (tels que int, char, double, etc.) sont transmis au corps de la fonction comme paramètres d'entrée, les paramètres passés deviennent des variables locales à l'intérieur du corps de fonction. Cette variable locale est une copie des paramètres d'entrée. Toutes les opérations à l'intérieur du corps de fonction sont des opérations de cette copie. Une fois l'exécution de la fonction terminée, la variable locale terminera sa mission et n'affectera pas les variables en tant que paramètres d'entrée. Cette méthode de passage des paramètres est appelée «passage de valeur». Dans Java, le passage utilisant un objet comme paramètre d'entrée est "Pass de référence", ce qui signifie qu'une seule "référence" de l'objet est passé. Le concept de cette "référence" est le même que la référence du pointeur dans le langage C. Lorsque la variable d'entrée est modifiée à l'intérieur du corps de la fonction, il s'agit essentiellement d'une opération directe sur l'objet.
Sauf pour "référence" lors de la réussite de la valeur de la fonction, "référence" lors de l'attribution de valeurs aux variables d'objet en utilisant "=". C'est similaire à donner à une variable un autre alias. Les deux noms pointent vers le même objet en mémoire.
Dans la programmation réelle, nous rencontrons souvent cette situation: il y a un objet A qui contient des valeurs valides à un certain moment. Pour le moment, un nouvel objet B peut être nécessaire exactement comme a, et toute modification de B n'affectera pas la valeur en A. c'est-à-dire que A et B sont deux objets indépendants, mais la valeur initiale de B est déterminée par l'objet A. Dans le langage Java, l'utilisation d'instructions de cession simples ne peut pas répondre à cette exigence. Bien qu'il existe de nombreuses façons de répondre à ce besoin, le moyen le plus simple et le plus efficace de mettre en œuvre la méthode clone () est parmi eux.
Toutes les classes Java héritent de la classe java.lang.object par défaut, et il existe une méthode clone () dans la classe java.lang.object. La documentation de l'API JDK explique que cette méthode renverra une copie de l'objet objet. Il y a deux points à expliquer: Premièrement, l'objet Copy renvoie un nouvel objet, pas une référence. Deuxièmement, la différence entre la copie d'un objet et un nouvel objet renvoyé avec le nouvel opérateur est que cette copie contient déjà des informations sur l'objet d'origine, plutôt que les informations initiales de l'objet.
Comment appliquer la méthode clone ()?
Un appel d'appel très typique à clone () est le suivant:
classe publique cloneclass implémente clonable {public int Aint; objet public clone () {clonEClass o = null; essayez {o = (cloneclass) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } retour o; }} Il y a trois points à noter. Tout d'abord, la classe Cloneclass qui espère implémenter la fonction CLONE implémente l'interface clonable. Cette interface appartient au package java.lang. Le package java.lang a été importé dans la classe par défaut, il n'a donc pas besoin d'être écrit comme java.lang.clonable. Une autre chose à noter est que la méthode clone () est surchargée. Enfin, super.clone () est appelé dans la méthode clone (), ce qui signifie également que peu importe à quoi ressemble la structure d'héritage de la classe de clone, super.clone () appelle directement ou indirectement la méthode clone () de la classe java.lang.object. Expliquons ces points en détail ci-dessous.
Il convient de dire que le troisième point est le plus important. Si vous observez soigneusement une méthode native du clone de classe d'objet (), l'efficacité de la méthode native est généralement beaucoup plus élevée que celle des méthodes non natives en Java. Cela explique également pourquoi nous devons utiliser la méthode clone () dans l'objet au lieu d'abord de la nouvelle classe A, puis attribuer les informations de l'objet d'origine au nouvel objet, bien que cela implémente également la fonction CLONE. Pour le deuxième point, nous devons également observer si Clone () dans la classe d'objets est une méthode avec une propriété protégée. Cela signifie également que si vous souhaitez appliquer la méthode Clone (), vous devez hériter de la classe d'objets. En Java, toutes les classes héritent de la classe d'objets par défaut, vous n'avez donc pas à vous en soucier. Ensuite, surchargez la méthode clone (). Une autre chose à considérer est que pour que d'autres classes appellent la méthode clone () de cette classe de clone, après la surcharge, les attributs de la méthode clone () doivent être définis en public.
Alors, pourquoi la classe Clone implémente-t-elle toujours l'interface clonable? Notez que l'interface clonable ne contient aucune méthode! En fait, cette interface n'est qu'un drapeau, et ce drapeau est uniquement pour la méthode clone () dans la classe d'objets. Si la classe Clone n'implémente pas l'interface clonable et appelle la méthode clone () de l'objet (c'est-à-dire que la méthode super.clone () est appelée), alors la méthode clone () de l'objet lancera une exception ClonenotsupportedException.
Ce qui précède est les étapes les plus élémentaires du clone. Si vous voulez terminer un clone réussi, vous devez également comprendre ce que sont "Clone Shadow" et "Deep Clone".
Qu'est-ce que Shadow Clone?
package com.zoer.src; classe UncLonea {private int i; public Unconea (int ii) {i = ii; } public void doubleValue () {i * = 2; } public String toString () {return Integer.ToString (i); }} classe CloneB implémente clonable {public int Aint; public Unconea unca = New Unconea (111); Clone d'objet public () {cloneB o = null; essayez {o = (cloneb) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } retour o; }} classe publique objRef {public static void main (String [] a) {cloneB b1 = new CloneB (); b1.AINT = 11; System.out.println ("Before Clone, B1.AINT =" + B1.AINT); System.out.println ("Before Clone, b1.unca =" + b1.unca); Cloneb b2 = (cloneb) b1.clone (); B2.AINT = 22; b2.unca.doubleValue (); System.out.println("======================================================================================================== =======================================================================. ========================================================================. =======================================================================. ========================================================================. =======================================================================. ========================================================================. clone, b2.AINT = "+ b2.aint); System.out.println ("After Clone, b2.unca =" + b2.unca); }} Résultat de sortie:
Avant clone, b1.aint = 11Before Clone, b1.unca = 111 ====================================================================. ======================================================================. =====================================================================. ======================================================================. =====================================================================. ======================================================================. =====================================================================. ======================================================================.
Les résultats de sortie montrent que la variable INT AINT et le résultat du clone de l'objet d'instance UnCa de Unconea sont incohérentes. Le type INT est vraiment un clone car la variable INT en B2 n'a aucun effet sur la neige de B1. C'est-à-dire, b2.AIN et b1.AIN occupe déjà différents espaces de mémoire, b2.AIN est une vraie copie de B1.AINT. Au contraire, le changement de B2.Unca change simultanément en b1.unca, et il est évident que b2.unca et b1.unca sont des références différentes qui ne pointent que vers le même objet! À partir de cela, nous pouvons voir que l'effet d'appeler la méthode Clone () dans la classe d'objets est: ouvrez d'abord un morceau d'espace en mémoire qui est le même que l'objet d'origine, puis copier le contenu dans l'objet d'origine tel qu'il est. Pour les types de données de base, ces opérations ne sont pas problématiques, mais pour les variables de type non primitives, nous savons qu'elles n'enregistrent que des références aux objets, ce qui provoque également les variables de type non primitif après le clone de pointer vers le même objet et les variables correspondantes dans l'objet d'origine.
La plupart du temps, les résultats de ce clone ne sont souvent pas les résultats que nous espérons, et ce clone est également appelé "Clone Shadow". Pour faire en sorte que B2.UNCA pointe vers un objet différent de B2.UNCA, et B2.UNCA contient également des informations dans B1.UNCA en tant qu'informations initiales, vous devez implémenter un clone de profondeur.
Comment effectuer un clone profond?
Il est très simple de changer l'exemple ci-dessus en un clone profond, et deux modifications sont nécessaires: l'une consiste à permettre à la classe Unconea d'implémenter également la même fonction de clone que la classe CLONEB (implémenter l'interface clonable et surcharger la méthode clone ()). La seconde consiste à ajouter une phrase o.unca = (unconea) unca.clone () à la méthode clone () de cloneB;
package com.zoer.src; La classe Unconea implémente clonable {private int i; public Unconea (int ii) {i = ii; } public void doubleValue () {i * = 2; } public String toString () {return Integer.ToString (i); } public objet clone () {Unconea o = null; essayez {o = (unconea) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } retour o; }} classe CloneB implémente clonable {public int Aint; public Unconea unca = New Unconea (111); Clone d'objet public () {cloneB o = null; essayez {o = (cloneb) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } o.unca = (unconea) unca.clone (); retour o; }} classe publique clonemain {public static void main (String [] a) {cloneB b1 = new CloneB (); b1.AINT = 11; System.out.println ("Before Clone, B1.AINT =" + B1.AINT); System.out.println ("Before Clone, b1.unca =" + b1.unca); Cloneb b2 = (cloneb) b1.clone (); B2.AINT = 22; b2.unca.doubleValue (); System.out.println("================================================================================ ========================================================================================================================. ========================================================================================================================. =======================================================================================================. clone, b1.aint = "+ b1.aint); System.out.println ("après clone, b1.unca =" + b1.unca); System.out.println("=================================================================================================== =====================================================================. =====================================================================. =====================================================================. =====================================================================. =====================================================================. =====================================================================. Résultat de sortie:
Avant clone, b1.aint = 11Before Clone, b1.unca = 111 ====================================================================. ======================================================================. =====================================================================. ======================================================================. =====================================================================. ======================================================================. =====================================================================. ======================================================================.
On peut voir que le changement actuel de b2.unca n'a aucun effet sur b1.unca. À l'heure actuelle, B1.UNCA et B2.UNCA pointent vers deux instances Unconea différentes, et B1 et B2 ont la même valeur au moment où CloneB B2 = (cloneB) b1.clone (); est appelé, ici, b1.i = b2.i = 11.
Vous devez savoir que toutes les classes ne peuvent pas mettre en œuvre des clones profonds. Par exemple, si vous modifiez la variable de type Unconea dans la classe CLONEB ci-dessus en type StringBuffer, consultez la description de StringBuffer dans l'API JDK. StringBuffer ne surcharge pas la méthode clone (), et ce qui est plus grave, c'est que StringBuffer est toujours une classe finale, ce qui signifie que nous ne pouvons pas implémenter indirectement le clone de StringBuffer en utilisant l'héritage. Si une classe contient un objet de type StringBuffer ou un objet similaire à StringBuffer, nous avons deux choix: soit seul le clone Shadow peut être implémenté, soit ajouter une phrase à la méthode Clone () de la classe (en supposant qu'il s'agit d'un objet SringBuffer (Unca.Tostring ()); // L'original est: o.unca = (unconea) unca.clone ();
Il convient également de noter qu'en plus des types de données de base qui peuvent implémenter automatiquement les clones profonds, l'objet String est une exception. Ses performances après clone semblent également mettre en œuvre des clones profonds. Bien que ce ne soit qu'une illusion, cela facilite considérablement notre programmation.
La différence entre String et StringBuffer dans Clone doit être expliqué que ce n'est pas un accent sur la différence entre String et StringBuffer, mais à partir de cet exemple, nous pouvons également voir des fonctionnalités uniques de la classe String.
L'exemple suivant comprend deux classes. La classe Clonec contient une variable de type String et une variable de type StringBuffer et implémente la méthode clone (). La variable de type clonec C1 est déclarée dans la classe Strclone, puis la méthode Clone () de C1 est appelée pour générer une copie de C1 C2. Après avoir modifié les variables de type String et StringBuffer en C2, le résultat est imprimé:
package com.zoer.src; Class clonec implémente clonable {public String Str; public StringBuffer Strbuff; Clone d'objet public () {clonec o = null; essayez {o = (clonec) super.clone (); } catch (clonenotsupportEdException e) {e.printStackTrace (); } retour o; }} classe publique strclone {public static void main (String [] a) {clonec c1 = new Clonec (); c1.str = new String ("initializest"); c1.strbuff = new StringBuffer ("initialIzestRbuff"); System.out.println ("Before Clone, c1.str =" + c1.str); System.out.println ("Before Clone, c1.strbuff =" + c1.strbuff); Clonec c2 = (clonec) c1.clone (); c2.str = c2.str.substring (0, 5); c2.strbuff = c2.strbuff.append ("Changer le clone strbuff"); System.out.println ("========================================================= ============================================================================. ============================================================================. ===============================================================================. ============================================================================. ============================================================================. ============================================================================. ===============================================================================. System.out.println ("After Clone, c2.strbuff =" + c2.strbuff); Résultats de l'exécution:
<span style = "Font-Family: 'Microsoft Yahei';"> <span style = "font-size: 16px;"> avant le clone, c1.str = initializestr avant clone, c1.strbuff = initiizestrBuff ============================================================================================================================. ============================================================================================================================. ============================================================================================================================. ============================================================================================================================. clone, c2.str = initializestrbuff change strbuff clone </span> </span>
Le résultat imprimé montre que les variables du type de chaîne semblent avoir implémenté le clone de profondeur, car les modifications de C2.Str n'ont pas affecté C1.Str! Java considère-t-il la classe Sring comme un type de données de base? En fait, ce n'est pas le cas. Il y a une petite astuce ici. Le secret réside dans l'énoncé C2.str = C2.str.Substring (0,5)! Essentiellement, C1.Str et C2.Str sont toujours des références lors du clone, et les deux pointent vers le même objet de chaîne. Mais lorsque C2.str = C2.str.Substring (0,5), il équivaut à générer un nouveau type de chaîne puis à l'attribuer à C2.str. En effet, String est écrit comme une classe immuable par les ingénieurs Sun, et les fonctions dans toutes les classes de chaînes ne peuvent pas modifier leurs propres valeurs.
Ce qui précède concerne cet article. J'espère qu'il sera utile pour tout le monde de comprendre une réplication profonde et superficielle à Java.