Java對(duì)象的復(fù)制三種方式(小結(jié))
1、概述
在實(shí)際編程過(guò)程中,我們常常要遇到這種情況:有一個(gè)對(duì)象A,在某一時(shí)刻A中已經(jīng)包含了一些有效值,此時(shí)可能 會(huì)需要一個(gè)和A完全相同新對(duì)象B,并且此后對(duì)B任何改動(dòng)都不會(huì)影響到A中的值,也就是說(shuō),A與B是兩個(gè)獨(dú)立的對(duì)象,但B的初始值是由A對(duì)象確定的。例如下面程序展示的情況:
class Student {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = stu1;
stu1.setNumber(54321);
System.out.println("學(xué)生1:" + stu1.getNumber());
System.out.println("學(xué)生2:" + stu2.getNumber());
}
}
結(jié)果:
學(xué)生1:54321
學(xué)生2:54321
為什么改變學(xué)生2的學(xué)號(hào),學(xué)生1的學(xué)號(hào)也發(fā)生了變化呢?
原因出在(stu2 = stu1) 這一句。該語(yǔ)句的作用是將stu1的引用賦值給stu2,
這樣,stu1和stu2指向內(nèi)存堆中同一個(gè)對(duì)象。如圖:

那么,怎么能干干凈凈清清楚楚地復(fù)制一個(gè)對(duì)象呢。在 Java語(yǔ)言中,用簡(jiǎn)單的賦值語(yǔ)句是不能滿(mǎn)足這種需求的。要滿(mǎn)足這種需求有很多途徑,
(1)將A對(duì)象的值分別通過(guò)set方法加入B對(duì)象中;
(2)通過(guò)重寫(xiě)java.lang.Object類(lèi)中的方法clone();
(3)通過(guò)org.apache.commons中的工具類(lèi)BeanUtils和PropertyUtils進(jìn)行對(duì)象復(fù)制;
(4)通過(guò)序列化實(shí)現(xiàn)對(duì)象的復(fù)制。
2、將A對(duì)象的值分別通過(guò)set方法加入B對(duì)象中
對(duì)屬性逐個(gè)賦值,本實(shí)例為了演示簡(jiǎn)單就設(shè)置了一個(gè)屬性:
Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = new Student(); stu2.setNumber(stu1.getNumber());
我們發(fā)現(xiàn),屬性少對(duì)屬性逐個(gè)賦值還挺方便,但是屬性多時(shí),就需要一直get、set了,非常麻煩。
3、重寫(xiě)java.lang.Object類(lèi)中的方法clone()
先介紹一下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。
在Java語(yǔ)言中,數(shù)據(jù)類(lèi)型分為值類(lèi)型(基本數(shù)據(jù)類(lèi)型)和引用類(lèi)型,值類(lèi)型包括int、double、byte、boolean、char等簡(jiǎn)單數(shù)據(jù)類(lèi)型,引用類(lèi)型包括類(lèi)、接口、數(shù)組等復(fù)雜類(lèi)型。淺克隆和深克隆的主要區(qū)別在于是否支持引用類(lèi)型的成員變量的復(fù)制,下面將對(duì)兩者進(jìn)行詳細(xì)介紹。
3.1 淺克隆
一般步驟:
被復(fù)制的類(lèi)需要實(shí)現(xiàn)Clonenable接口(不實(shí)現(xiàn)的話(huà)在調(diào)用clone方法會(huì)拋出CloneNotSupportedException異常), 該接口為標(biāo)記接口(不含任何方法)
覆蓋clone()方法,訪(fǎng)問(wèn)修飾符設(shè)為public。方法中調(diào)用super.clone()方法得到需要的復(fù)制對(duì)象。(native為本地方法)
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("學(xué)生1:" + stu1.getNumber());
System.out.println("學(xué)生2:" + stu2.getNumber());
stu2.setNumber(54321);
System.out.println("學(xué)生1:" + stu1.getNumber());
System.out.println("學(xué)生2:" + stu2.getNumber());
}
}
結(jié)果:
學(xué)生1:12345
學(xué)生2:12345
學(xué)生1:12345
學(xué)生2:54321
在淺克隆中,如果原型對(duì)象的成員變量是值類(lèi)型,將復(fù)制一份給克隆對(duì)象;如果原型對(duì)象的成員變量是引用類(lèi)型,則將引用對(duì)象的地址復(fù)制一份給克隆對(duì)象,也就是說(shuō)原型對(duì)象和克隆對(duì)象的成員變量指向相同的內(nèi)存地址。
簡(jiǎn)單來(lái)說(shuō),在淺克隆中,當(dāng)對(duì)象被復(fù)制時(shí)只復(fù)制它本身和其中包含的值類(lèi)型的成員變量,而引用類(lèi)型的成員對(duì)象并沒(méi)有復(fù)制。

在Java語(yǔ)言中,通過(guò)覆蓋Object類(lèi)的clone()方法可以實(shí)現(xiàn)淺克隆。
3.2 深克隆
package abc;
class Address {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); //淺復(fù)制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖區(qū)");
System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
學(xué)生1:123,地址:西湖區(qū)
學(xué)生2:123,地址:西湖區(qū)
怎么兩個(gè)學(xué)生的地址都改變了?
原因是淺復(fù)制只是復(fù)制了addr變量的引用,并沒(méi)有真正的開(kāi)辟另一塊空間,將值復(fù)制后再將引用返回給新對(duì)象。
為了達(dá)到真正的復(fù)制對(duì)象,而不是純粹引用復(fù)制。我們需要將Address類(lèi)可復(fù)制化,并且修改clone方法,完整代碼如下:
package abc;
class Address implements Cloneable {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); //淺復(fù)制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address)addr.clone(); //深度復(fù)制
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖區(qū)");
System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
學(xué)生1:123,地址:西湖區(qū)
學(xué)生2:123,地址:杭州市
在深克隆中,無(wú)論原型對(duì)象的成員變量是值類(lèi)型還是引用類(lèi)型,都將復(fù)制一份給克隆對(duì)象,深克隆將原型對(duì)象的所有引用對(duì)象也復(fù)制一份給克隆對(duì)象。
簡(jiǎn)單來(lái)說(shuō),在深克隆中,除了對(duì)象本身被復(fù)制外,對(duì)象所包含的所有成員變量也將復(fù)制。

在Java語(yǔ)言中,如果需要實(shí)現(xiàn)深克隆,可以通過(guò)覆蓋Object類(lèi)的clone()方法實(shí)現(xiàn),也可以通過(guò)序列化(Serialization)等方式來(lái)實(shí)現(xiàn)。
(如果引用類(lèi)型里面還包含很多引用類(lèi)型,或者內(nèi)層引用類(lèi)型的類(lèi)里面又包含引用類(lèi)型,使用clone方法就會(huì)很麻煩。這時(shí)我們可以用序列化的方式來(lái)實(shí)現(xiàn)對(duì)象的深克隆。)
4、工具類(lèi)BeanUtils和PropertyUtils進(jìn)行對(duì)象復(fù)制
Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = new Student(); BeanUtils.copyProperties(stu2,stu1);
這種寫(xiě)法無(wú)論多少種屬性都只需要一行代碼搞定,很方便吧!除BeanUtils外還有一個(gè)名為PropertyUtils的工具類(lèi),它也提供copyProperties()方法,作用與BeanUtils的同名方法十分相似,主要的區(qū)別在于BeanUtils提供類(lèi)型轉(zhuǎn)換功能,即發(fā)現(xiàn)兩個(gè)JavaBean的同名屬性為不同類(lèi)型時(shí),在支持的數(shù)據(jù)類(lèi)型范圍內(nèi)進(jìn)行轉(zhuǎn)換,而PropertyUtils不支持這個(gè)功能,但是速度會(huì)更快一些。在實(shí)際開(kāi)發(fā)中,BeanUtils使用更普遍一點(diǎn),犯錯(cuò)的風(fēng)險(xiǎn)更低一點(diǎn)。
5、通過(guò)序列化實(shí)現(xiàn)對(duì)象的復(fù)制
序列化就是將對(duì)象寫(xiě)到流的過(guò)程,寫(xiě)到流中的對(duì)象是原有對(duì)象的一個(gè)拷貝,而原對(duì)象仍然存在于內(nèi)存中。通過(guò)序列化實(shí)現(xiàn)的拷貝不僅可以復(fù)制對(duì)象本身,而且可以復(fù)制其引用的成員對(duì)象,因此通過(guò)序列化將對(duì)象寫(xiě)到一個(gè)流中,再?gòu)牧骼飳⑵渥x出來(lái),可
以實(shí)現(xiàn)深克隆。需要注意的是能夠?qū)崿F(xiàn)序列化的對(duì)象其類(lèi)必須實(shí)現(xiàn)Serializable接口,否則無(wú)法實(shí)現(xiàn)序列化操作。
- 利用Java反射機(jī)制實(shí)現(xiàn)對(duì)象相同字段的復(fù)制操作
- Java編程實(shí)現(xiàn)對(duì)象克隆(復(fù)制)代碼詳解
- Java對(duì)象深復(fù)制與淺復(fù)制實(shí)例詳解
- Java中對(duì)象的深復(fù)制(深克?。┖蜏\復(fù)制(淺克?。┙榻B
- 深入java對(duì)象復(fù)制的分析
- Java基礎(chǔ)之面向?qū)ο髾C(jī)制(多態(tài)、繼承)底層實(shí)現(xiàn)
- Java MapStruct解了對(duì)象映射的毒
- 詳解Java對(duì)象的內(nèi)存布局
- Java基礎(chǔ)詳解之面向?qū)ο蟮哪切┦聝?/a>
- 深入理解Java對(duì)象復(fù)制
相關(guān)文章
JavaCV調(diào)用百度AI實(shí)現(xiàn)人臉檢測(cè)方法詳解
在檢測(cè)人臉數(shù)量、位置、性別、口罩等場(chǎng)景時(shí),可以考慮使用百度開(kāi)放平臺(tái)提供的web接口,一個(gè)web請(qǐng)求就能完成檢測(cè)得到結(jié)果。本文就為大家介紹JavaCV如何調(diào)用百度AI實(shí)現(xiàn)最簡(jiǎn)單的人臉檢測(cè),需要的可以參考一下2022-01-01
詳解MyBatis-Puls中saveBatch批量添加慢的問(wèn)題
本文主要介紹了詳解MyBatis-Puls中saveBatch批量添加慢的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
解決idea?中?SpringBoot?點(diǎn)擊運(yùn)行沒(méi)反應(yīng)按鈕成灰色的問(wèn)題
在使用 Spring Boot 開(kāi)發(fā)項(xiàng)目時(shí),可能會(huì)遇到一個(gè)問(wèn)題:點(diǎn)擊運(yùn)行按鈕后,控制臺(tái)沒(méi)有任何輸出,項(xiàng)目界面也沒(méi)有顯示,這種情況可能是由多種原因?qū)е碌?,本文將介紹一些常見(jiàn)的解決方法,需要的朋友可以參考下2023-08-08
Java?List集合取交集的五種常見(jiàn)方式總結(jié)
在Java中取兩個(gè)List集合的交集可以通過(guò)多種方式實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于Java?List集合取交集的五種常見(jiàn)方式,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07
關(guān)于Java的對(duì)象序列化流和反序列化流詳細(xì)解讀
這篇文章主要介紹了關(guān)于Java的對(duì)象序列化流和反序列化流,對(duì)象序列化:就是將對(duì)象保存到磁盤(pán)中,或者在網(wǎng)絡(luò)中傳輸對(duì)象,反之,自己序列還可以從文件中讀取回來(lái),重構(gòu)對(duì)象,對(duì)它進(jìn)行反序列化,需要的朋友可以參考下2023-05-05
mybatis?mapper.xml中如何根據(jù)數(shù)據(jù)庫(kù)類(lèi)型選擇對(duì)應(yīng)SQL語(yǔ)句
這篇文章主要介紹了mybatis?mapper.xml中如何根據(jù)數(shù)據(jù)庫(kù)類(lèi)型選擇對(duì)應(yīng)SQL語(yǔ)句,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01

