浅拷贝
Java的深拷贝和浅拷贝,其实现方式正是通过调用Object类的clone()方法来完成
protected Object clone()
throws CloneNotSupportedException创建并返回此对象的副本。 “复制”的精确含义可能取决于对象的类。 一般的意图是,对于任何对象x ,表达式:
x.clone() != x将是真实的,而且表达:
x.clone().getClass() == x.getClass()将是true ,但这些都不是绝对的要求。 通常情况是:
x.clone().equals(x)将是true ,这不是一个绝对的要求。
按照惯例,返回的对象应该通过调用super.clone获得。 如果一个类和它的所有超类(除了Object )遵守这个惯例,那将是x.clone().getClass() == x.getClass()的情况。
按照惯例,此方法返回的对象应该与此对象(正被克隆)无关。 为了实现这一独立性,可能需要修改super.clone返回的对象的一个或多个字段。 通常,这意味着复制构成被克隆的对象的内部“深层结构”的任何可变对象,并通过引用该副本替换对这些对象的引用。 如果一个类仅包含原始字段或对不可变对象的引用,则通常情况下, super.clone返回的对象中的字段通常不需要修改。
clone的方法Object执行特定的克隆操作。 首先,如果此对象的类不实现接口Cloneable ,则抛出CloneNotSupportedException 。 请注意,所有数组都被认为是实现接口Cloneable ,并且数组类型T[]的clone方法的返回类型是T[] ,其中T是任何引用或原始类型。 否则,该方法将创建该对象的类的新实例,并将其所有字段初始化为完全符合该对象的相应字段的内容,就像通过赋值一样。 这些字段的内容本身不被克隆。 因此,该方法执行该对象的“浅拷贝”,而不是“深度拷贝”操作。
Object类本身并不实现接口Cloneable ,因此在类别为Object的对象上调用clone方法将导致运行时抛出异常。
结果
这个实例的一个克隆。
异常
CloneNotSupportedException - 如果对象的类不支持Cloneable接口。 覆盖clone方法的子类也可以抛出此异常以指示实例无法克隆。
demo:
package com.al.o2o.demo;
/**
* @author Xiahuicheng
* 既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝
* Address implements Cloneable
*/
public class Address {
private String provices;
private String city;
public void setAddress(String provices,String city){
this.provices = provices;
this.city = city;
}
@Override
public String toString() {
return "Address [provices=" + provices + ", city=" + city + "]";
}
}
package com.al.o2o.demo;
public class Person implements Cloneable {
public String pname;
public int page;
public Address address;
public Person() {}
public Person(String pname,int page){
this.pname = pname;
this.page = page;
this.address = new Address();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void setAddress(String provices,String city ){
address.setAddress(provices, city);
}
public void display(String name){
System.out.println(name+":"+"pname=" + pname + ", page=" + page +","+ address);
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
}
- 这是一个我们要进行赋值的原始类 Person。下面我们产生一个 Person 对象,并调用其 clone 方法复制一个新的对象。
注意:调用对象的 clone 方法,必须要让类实现 Cloneable 接口,并且覆写 clone 方法
@Test
public void testShallClone() throws Exception{
Person p1 = new Person("zhangsan",21);
p1.setAddress("湖北","武汉");
Person p2 = (Person) p1.clone();
System.out.println("p1_code:"+p1);
System.out.println("p1pname:"+p1.getPname().hashCode());
System.out.println("p2_code:"+p2);
System.out.println("p1pname:"+p2.getPname().hashCode());
p1.display("p1");
p2.display("p2");
p2.setAddress("湖北","荆州");
System.out.println("修改地址后");
p1.display("p1");
p2.display("p2");
执行结果:
p1_code:com.al.o2o.demo.Person@15615099 //1
p1pname:-1432604556 //2
p2_code:com.al.o2o.demo.Person@1edf1c96 //3
p1pname:-1432604556 //4
p1:pname=zhangsan, page=21,Address [provices=湖北, city=武汉] //5
p2:pname=zhangsan, page=21,Address [provices=湖北, city=武汉] //6
修改地址后
p1:pname=zhangsan, page=21,Address [provices=湖北, city=荆州] //7
p2:pname=zhangsan, page=21,Address [provices=湖北, city=荆州] //8
-
首先看原始类Person实现了Cloneable接口,并且覆写了clone方法,它还有三个属性,一个引用类型 String定义的 pname,一个基本类型 int定义的 page,还有一个引用类型 Address ,这是一个自定义类,这个类也包含两个属性 provices 和 city 接着看测试内容,首先我们创建一个Person 类的对象 p1,其pname 为zhangsan,page为21,地址类 Address 两个属性为 湖北省和武汉市。接着我们调用 clone() 方法复制另一个对象 p2,接着打印这两个对象的内容。
从第 1 行和第 3 行打印结果:
p1_code:com.al.o2o.demo.Person@15615099
p2_code:com.al.o2o.demo.Person@1edf1c96
可以看出这是两个不同的对象。
从第 5 行和第 6 行打印的对象内容看,原对象 p1 和克隆出来的对象 p2 内容完全相同。
代码中我们只是更改了克隆对象 p2 的属性 Address 为湖北省荆州市(原对象 p1 是 湖北省武汉市) ,但是从第 7 行和第 8 行打印结果来看,原对象 p1 和克隆对象 p2 的 Address 属性都被修改了。
也就是说对象 Person 的属性 Address,经过 clone 之后,其实只是复制了其引用,他们指向的还是同一块堆内存空间,当修改其中一个对象的属性 Address,另一个也会跟着变化。
浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。
深拷贝
深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。
如何实现深拷贝
既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝。比如上面的例子,Person 类有一个引用类型 Address(其实String 也是引用类型,但是String类型有点特殊,后面会详细讲解),我们在 Address 类内部也重写 clone 方法。如下:
```java
public class Address implements Cloneable{
4 private String provices;
5 private String city;
6 public void setAddress(String provices,String city){
7 this.provices = provices;
8 this.city = city;
9 }
10 @Override
11 public String toString() {
12 return "Address [provices=" + provices + ", city=" + city + "]";
13 }
14 @Override
15 protected Object clone() throws CloneNotSupportedException {
16 return super.clone();
17 }
18
19 }
```
```java
1 //Person.class的clone()方法
@Override
2 protected Object clone() throws CloneNotSupportedException {
3 Person p = (Person) super.clone();
4 p.address = (Address) address.clone();
5 return p;
6 }
```
- 测试还是和上面一样,我们会发现更改了p2对象的Address属性,p1 对象的 Address 属性并没有变化。但是这种做法有个弊端,这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法,但是如果 Address 类也存在一个引用类型,那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。
-
利用序列化
序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。
注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外
public static void main(String[] args) throws IOException { ObjectOutputStream oos = null; ObjectInputStream ois = null; try { // 创建原始的可序列化对象 Person p1 = new Person("ZHANGSHAN",21); p1.setAddress("湖北","武汉"); System.out.println("p1:"+p1); p1.display("p1"); Person p2 = null; //通过序列化实现深拷贝 ByteArrayOutputStream bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); // 序列化以及传递这个对象 oos.writeObject(p1); oos.flush(); ByteArrayInputStream bin =new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bin); // 返回新的对象 p2 = (Person) ois.readObject(); System.out.println("p2:"+p2); p2.display("p2"); //改变p2地址 System.out.println("改变p2对象的内容"); p2.setAddress("湖南","长沙"); //查看每一个现在的内容 p1.display("p1"); p2.display("p2"); }catch (Exception e){ System.out.println("Exception in main =" +e); }finally { oos.close(); ois.close(); } } runting: p1:com.al.o2o.demo.Person@452b3a41 p1:pname=ZHANGSHAN, page=21,Address [provices=湖北, city=武汉] p2:com.al.o2o.demo.Person@254989ff p2:pname=ZHANGSHAN, page=21,Address [provices=湖北, city=武汉] 改变p2对象的内容 p1:pname=ZHANGSHAN, page=21,Address [provices=湖北, city=武汉] p2:pname=ZHANGSHAN, page=21,Address [provices=湖南, city=长沙]
因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。
基本类型和引用类型
- 在Java中可以分为两大类:基本类型和引用类型
- 基本类型也称为值类型,分别是字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double
- 引用类型包括类、接口、数组、枚举等等
- java将内存空间分为堆和栈,基本类型直接在栈中存储数值,而引用类型是将引用放到栈中,实际存储的值是放在堆中,通过栈中的引用指向堆中存放的数据。
- 上图定义的 a 和 b 都是基本类型,其值是直接存放在栈中的;而 c 和 d 是 String 声明的,这是一个引用类型,引用地址是存放在 栈中,然后指向堆的内存空间。下面 d = c;这条语句表示将 c 的引用赋值给 d,那么 c 和 d 将指向同一块堆内存空间
https://www.cnblogs.com/ysocean/p/8482979.html