作者:小牛呼嚕嚕 | https://xiaoniuhululu.com 電腦內功、JAVA底層、面試相關資料等更多精彩文章在公眾號「小牛呼嚕嚕 」 引用拷貝 引用拷貝: 引用拷貝不會在堆上創建一個新的對象,只 會在棧上生成一個新的引用地址,最終指向依然是堆上的同一個對象。 //實體類 publi ...
目錄
作者:小牛呼嚕嚕 | https://xiaoniuhululu.com
電腦內功、JAVA底層、面試相關資料等更多精彩文章在公眾號「小牛呼嚕嚕 」
引用拷貝
引用拷貝: 引用拷貝不會在堆上創建一個新的對象,只 會在棧上生成一個新的引用地址,最終指向依然是堆上的同一個對象。
//實體類
public class Person{
public String name;//姓名
public int height;//身高
public StringBuilder something;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public StringBuilder getSomething() {
return something;
}
public void setSomething(StringBuilder something) {
this.something = something;
}
public Person(String name, int height, StringBuilder something) {
this.name = name;
this.height = height;
this.something = something;
}
}
//測試類
public class copyTest {
public static void main(String[] args) {
Person p1 = new Person("小張", 180, new StringBuilder("今天天氣很好"));
Person p2 = p1;
System.out.println("對象是否相等:"+ (p1 == p2));
System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());
// change
p1.name="小王";
p1.height = 200;
p1.something.append(",適合出去玩");
System.out.println("...after p1 change....");
System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());
}
}
結果:
對象是否相等:true
p1 屬性值=小張,180,今天天氣很好
p2 屬性值=小張,180,今天天氣很好
...after p1 change....
p1 屬性值=小王,200,今天天氣很好,適合出去玩
p2 屬性值=小王,200,今天天氣很好,適合出去玩
before change:
after change:
我們可以看出 由於2個引用p1,p2 都是指向堆中同一個對象,所以2個對象是相等的,修改了對象p1,會影響到對象p2
需要註意的
- name屬性,雖然她是引用類型,但她同時也是String類型,不可變,對其修改,JVM會預設在堆上創建新的記憶體空間,再重新賦值
int weight=180;
是成員變數,存放在堆中,不是所有的基本類型變數 都存放在JVM棧中
註意與這篇文章得區分開來 https://mp.weixin.qq.com/s/6qRspyLAsoBxttGwGtxsAA, int num1 = 10;
是基本類型的局部變數
,存放在棧中
淺拷貝
淺拷貝 :淺拷貝會在堆上創建一個新的對象,新對象和原對象不等,但是新對象的屬性和老對象相同。
其中:
- 如果屬性是基本類型(int,double,long,boolean等),拷貝的就是基本類型的值。
- 如果屬性是引用類型(除了基本類型都是引用類型),拷貝的就是引⽤數據類型變數的地址值,⽽對於引⽤類型變數指向的堆中的對象不會拷貝。
如何實現淺拷貝呢?也很簡單,就是在需要拷貝的類上實現Cloneable介面並重寫其clone()方法。
@Override protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
在使用的時候直接調用類的clone()方法即可
//實體類 繼承Cloneable
public class Person implements Cloneable{
public String name;//姓名
public int height;//身高
public StringBuilder something;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public StringBuilder getSomething() {
return something;
}
public void setSomething(StringBuilder something) {
this.something = something;
}
public Person(String name, int height, StringBuilder something) {
this.name = name;
this.height = height;
this.something = something;
}
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
//測試類
public class shallowCopyTest {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person("小張", 180, new StringBuilder("今天天氣很好"));
Person p2 = p1.clone();
System.out.println("對象是否相等:"+ (p1 == p2));
System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());
// change
p1.setName("小王");
p1.setHeight(200);
p1.getSomething().append(",適合出去玩");
System.out.println("...after p1 change....");
System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());
}
}
結果:
對象是否相等:false
p1 屬性值=小張,180,今天天氣很好
p2 屬性值=小張,180,今天天氣很好
...after p1 change....
p1 屬性值=小王,200,今天天氣很好,適合出去玩
p2 屬性值=小張,180,今天天氣很好,適合出去玩
before change:
after change:
我們可以看出:
- 當我們修改對象p1的weight屬性時,由於p2的height屬性 是直接複製修改前的p1的height屬性,所以還是180。
- 當我們修改對象p1的name屬性 時,String name指向一個新的記憶體空間,但對象p2的name還是指向舊的記憶體空間,所以對象p2的name屬性還是"小張"。
- 由於對象p1的something屬性和對象p2的something屬性指向是同一個記憶體空間,當我們修改對象p1的something屬性,會影響到對象p2的something屬性,所以對象p2的something屬性變為"今天天氣很好,適合出去玩"。
深拷貝
深拷貝 :完全拷貝⼀個對象,在堆上創建一個新的對象,拷貝被拷貝對象的成員變數的值,同時堆中的對象也會拷貝。
需要重寫clone方法
@Override
public Person clone() throws CloneNotSupportedException {
//return (Person) super.clone();
Person person = (Person) super.clone();
person.setSomething( new StringBuilder(person.getSomething()));//單獨為引用類型clone
return person;
}
shallowCopyTest測試類的結果:
對象是否相等:false
p1 屬性值=小張,180,今天天氣很好
p2 屬性值=小張,180,今天天氣很好
...after p1 change....
p1 屬性值=小王,200,今天天氣很好,適合出去玩
p2 屬性值=小張,180,今天天氣很好
這時候對象p1和對象p2互不幹擾了
before change:
after change:
但這樣也有個小問題,對象每有一個引用類型,我們都得重寫其clone方法,這樣會非常麻煩,因此我們還可以藉助序列化來實現對象的深拷貝
//實體類 繼承Cloneable
public class Person implements Serializable{
public String name;//姓名
public int height;//身高
public StringBuilder something;
...//省略 getter setter
public Object deepClone() throws Exception{
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
//測試類,這邊類名筆者就不換了,在之前的基礎上改改
public class shallowCopyTest {
public static void main(String[] args) throws Exception {
Person p1 = new Person("小張", 180, new StringBuilder("今天天氣很好"));
Person p2 = (Person)p1.deepClone();
System.out.println("對象是否相等:"+ (p1 == p2));
System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());
// change
p1.setName("小王");
p1.setHeight(200);
p1.getSomething().append(",適合出去玩");
System.out.println("...after p1 change....");
System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());
}
}
這樣也會得到深拷貝的結果
小結
- 引用拷貝: 引用拷貝不會在堆上創建一個新的對象,只 會在棧上生成一個新的引用地址,最終指向依然是堆上的同一個對象。
- 淺拷貝 :淺拷貝會在堆上創建一個新的對象,新對象和原對象不等,但是新對象的屬性和老對象相同。
其中:
- 如果屬性是基本類型(int,double,long,boolean等),拷貝的就是基本類型的值。
- 如果屬性是引用類型(除了基本類型都是引用類型),拷貝的就是引⽤數據類型變數的地址值,⽽對於引⽤類型變數指向的堆中的對象不會拷貝。
- 深拷貝 :完全拷貝⼀個對象,在堆上創建一個新的對象,拷貝被拷貝對象的成員變數的值,同時堆中的對象也會拷貝。
參考資料:
https://blog.csdn.net/JingLxian/article/details/106337395
https://www.cnblogs.com/hithlb/p/4872373.html
很感謝你能看到最後,如果喜歡的話,歡迎關註點贊收藏轉發,謝謝!更多精彩的文章