Maison >Java >javaDidacticiel >Introduction à la copie profonde et à la copie superficielle en Java
1. Introduction
La copie d'objet consiste à copier les propriétés d'un objet vers un autre objet du même type de classe. Il est très courant de copier des objets dans un programme, principalement pour réutiliser tout ou partie des données de l'objet dans un nouveau contexte. Il existe trois types de copies d'objets en Java : Shallow Copy, Deep Copy et Lazy Copy.
2. Copie superficielle
1. Qu'est-ce qu'une copie superficielle
La copie superficielle est une copie bit par bit d'un objet qui a une copie de l'original. valeurs d'attribut de l'objet. Si l'attribut est un type de base, la valeur du type de base est copiée ; si l'attribut est une adresse mémoire (type référence), l'adresse mémoire est copiée, donc si l'un des objets change cette adresse, cela affectera l'autre. objet.
Dans la figure, SourceObject a un attribut de type int "field1" et un attribut de type référence "refObj" (faisant référence à un objet de type ContainedObject). Lors d'une copie superficielle du SourceObject, un CopiedObject est créé avec une propriété « field2 » qui contient la valeur copiée de « field1 » et une référence qui pointe toujours vers refObj lui-même. Puisque "field1" est un type de base, sa valeur est simplement copiée dans "field2", mais comme "refObj" est un type référence, CopiedObject pointe vers la même adresse que "refObj". Par conséquent, toute modification apportée à "refObj" dans SourceObject affectera CopiedObject.
2. Comment implémenter une copie superficielle
Ce qui suit est un exemple d'implémentation d'une copie superficielle
public class Subject { private String name; public Subject(String s) { name = s; } public String getName() { return name; } public void setName(String s) { name = s; } } public class Student implements Cloneable { // 对象引用 private Subject subj; private String name; public Student(String s, String sub) { name = s; subj = new Subject(sub); } public Subject getSubj() { return subj; } public String getName() { return name; } public void setName(String s) { name = s; } /** * 重写clone()方法 * @return */ public Object clone() { //浅拷贝 try { // 直接调用父类的clone()方法 return super.clone(); } catch (CloneNotSupportedException e) { return null; } } } public class CopyTest { public static void main(String[] args) { // 原始对象 Student stud = new Student("John", "Algebra"); System.out.println("Original Object: " + stud.getName() + " - " + stud.getSubj().getName()); // 拷贝对象 Student clonedStud = (Student) stud.clone(); System.out.println("Cloned Object: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName()); // 原始对象和拷贝对象是否一样: System.out.println("Is Original Object the same with Cloned Object: " + (stud == clonedStud)); // 原始对象和拷贝对象的name属性是否一样 System.out.println("Is Original Object's field name the same with Cloned Object: " + (stud.getName() == clonedStud.getName())); // 原始对象和拷贝对象的subj属性是否一样 System.out.println("Is Original Object's field subj the same with Cloned Object: " + (stud.getSubj() == clonedStud.getSubj())); stud.setName("Dan"); stud.getSubj().setName("Physics"); System.out.println("Original Object after it is updated: " + stud.getName() + " - " + stud.getSubj().getName()); System.out.println("Cloned Object after updating original object: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName()); } }
Le résultat de sortie est le suivant :
Objet original : John - Algèbre
Objet cloné : John - Algèbre
L'objet d'origine est-il le même que l'objet cloné : false
Le nom du champ de l'objet d'origine est-il le même avec l'objet cloné : vrai
Le champ de l'objet d'origine est-il le même avec Objet cloné : vrai
Objet original après sa mise à jour : Dan - Physique
Objet cloné après mise à jour de l'objet original : John - Physique
Dans cet exemple, je laisse la classe Étudiant à copier implémenter l'interface Clonable et réécrivez la méthode clone() de la classe Object, puis appelez la méthode super.clone() à l'intérieur de la méthode. À partir des résultats de sortie, nous pouvons voir que les modifications apportées à l'attribut "name" de l'objet de référence stud n'ont pas affecté l'objet copié clonedStud, mais les modifications apportées à l'attribut "name" de l'objet de référence subj ont affecté l'objet copié. ClonéStud.
3. Copie profonde
1. Qu'est-ce que la copie profonde
La copie profonde copiera tous les attributs et copiera la mémoire allouée dynamiquement pointée par les attributs. Une copie complète se produit lorsqu'un objet est copié avec les objets auxquels il fait référence. La copie approfondie est plus lente et plus coûteuse que la copie superficielle.
Dans la figure ci-dessus, SourceObject a une propriété de type int "field1" et une propriété de type référence "refObj1" (faisant référence à un objet de type ContainedObject). Lors d'une copie complète du SourceObject, un CopiedObject est créé, qui possède une propriété « field2 » contenant la valeur copiée de « field1 » et une propriété de type référence « refObj2 » contenant la valeur copiée de « refObj1 ». Par conséquent, toute modification apportée à "refObj" dans SourceObject n'affectera pas CopiedObject
2. Comment implémenter la copie approfondie
Ce qui suit est un exemple d'implémentation de la copie approfondie. Seule une petite modification a été apportée à l’exemple de copie superficielle, et les classes Subject et CopyTest n’ont pas changé.
public class Student implements Cloneable { // 对象引用 private Subject subj; private String name; public Student(String s, String sub) { name = s; subj = new Subject(sub); } public Subject getSubj() { return subj; } public String getName() { return name; } public void setName(String s) { name = s; } /** * 重写clone()方法 * * @return */ public Object clone() { // 深拷贝,创建拷贝类的一个新对象,这样就和原始对象相互独立 Student s = new Student(name, subj.getName()); return s; } }
Les résultats de sortie sont les suivants :
Objet d'origine : John - Algèbre
Objet cloné : John - Algèbre
L'objet d'origine est-il le même que l'objet cloné : false
Le nom du champ de l'objet d'origine est-il le même avec l'objet cloné : vrai
Le champ de l'objet d'origine est-il le même avec l'objet cloné : faux
L'objet d'origine après sa mise à jour : Dan - Physique
Objet cloné après la mise à jour de l'objet d'origine : John - Algèbre
Il est facile de trouver un petit changement dans la méthode clone(). Puisqu'il s'agit d'une copie complète, vous devez créer un objet de la classe copy. Comme il existe une référence d'objet dans la classe Student, vous devez implémenter l'interface Cloneable et remplacer la méthode clone dans la classe Student.
3、通过序列化实现深拷贝
也可以通过序列化来实现深拷贝。序列化是干什么的?它将整个对象图写入到一个持久化存储文件中并且当需要的时候把它读取回来, 这意味着当你需要把它读取回来时你需要整个对象图的一个拷贝。这就是当你深拷贝一个对象时真正需要的东西。请注意,当你通过序列化进行深拷贝时,必须确保对象图中所有类都是可序列化的。
public class ColoredCircle implements Serializable { private int x; private int y; public ColoredCircle(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } @Override public String toString() { return "x=" + x + ", y=" + y; } } public class DeepCopy { public static void main(String[] args) throws IOException { ObjectOutputStream oos = null; ObjectInputStream ois = null; try { // 创建原始的可序列化对象 ColoredCircle c1 = new ColoredCircle(100, 100); System.out.println("Original = " + c1); ColoredCircle c2 = null; // 通过序列化实现深拷贝 ByteArrayOutputStream bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); // 序列化以及传递这个对象 oos.writeObject(c1); oos.flush(); ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bin); // 返回新的对象 c2 = (ColoredCircle) ois.readObject(); // 校验内容是否相同 System.out.println("Copied = " + c2); // 改变原始对象的内容 c1.setX(200); c1.setY(200); // 查看每一个现在的内容 System.out.println("Original = " + c1); System.out.println("Copied = " + c2); } catch (Exception e) { System.out.println("Exception in main = " + e); } finally { oos.close(); ois.close(); } } }
输出结果如下:
Original = x=100, y=100
Copied = x=100, y=100
Original = x=200, y=200
Copied = x=100, y=100
这里,你只需要做以下几件事儿:
(1)确保对象图中的所有类都是可序列化的
(2)创建输入输出流
(3)使用这个输入输出流来创建对象输入和对象输出流
(4)将你想要拷贝的对象传递给对象输出流
(5)从对象输入流中读取新的对象并且转换回你所发送的对象的类
在这个例子中,我创建了一个ColoredCircle对象c1然后将它序列化 (将它写到ByteArrayOutputStream中). 然后我反序列化这个序列化后的对象并将它保存到c2中。随后我修改了原始对象c1。然后结果如你所见,c1不同于c2,对c1所做的任何修改都不会影响c2。
注意,序列化这种方式有其自身的限制和问题:
因为无法序列化transient变量, 使用这种方法将无法拷贝transient变量。
再就是性能问题。创建一个socket, 序列化一个对象, 通过socket传输它, 然后反序列化它,这个过程与调用已有对象的方法相比是很慢的。所以在性能上会有天壤之别。如果性能对你的代码来说是至关重要的,建议不要使用这种方式。它比通过实现Clonable接口这种方式来进行深拷贝几乎多花100倍的时间。
四、延迟拷贝
延迟拷贝是浅拷贝和深拷贝的一个组合,实际上很少会使用。 当最开始拷贝一个对象时,会使用速度较快的浅拷贝,还会使用一个计数器来记录有多少对象共享这个数据。当程序想要修改原始的对象时,它会决定数据是否被共享(通过检查计数器)并根据需要进行深拷贝。
延迟拷贝从外面看起来就是深拷贝,但是只要有可能它就会利用浅拷贝的速度。当原始对象中的引用不经常改变的时候可以使用延迟拷贝。由于存在计数器,效率下降很高,但只是常量级的开销。而且, 在某些情况下, 循环引用会导致一些问题。
五、如何选择
如果对象的属性全是基本类型的,那么可以使用浅拷贝,但是如果对象有引用属性,那就要基于具体的需求来选择浅拷贝还是深拷贝。我的意思是如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。没有一成不变的规则,一切都取决于具体需求。
更多Introduction à la copie profonde et à la copie superficielle en Java相关文章请关注PHP中文网!