文/阿敏其人
本文出自阿敏其人簡書博客他嫡,轉(zhuǎn)載請與本人聯(lián)系徘熔。
為什么要拷貝對象
我們干嘛要去拷貝一個對象呢?
對于前端來說窒升,這種情況多發(fā)于接收了后端的數(shù)據(jù)台谊,但是在界面展示上數(shù)據(jù)不夠完整锅铅,后端不改數(shù)據(jù),這時候就要你自己來動手拷貝對象了玩荠。
對應后端來說阶冈,多發(fā)于造數(shù)據(jù)給前端,為了配合前端填具。
干巴巴的文字不好看匆骗,我來努力找個栗子吧。
假設(shè)你是個前端盟广,做的是一個電商項目铝噩。每一個商品都有一個 名稱 ,價格 具被,商品id只损。
然后,根據(jù)后端的返回跃惫,你知道需要如下一個bean爆存。
public class Phone {
public String name;
public double price;
public int goodsId ;
}
問題來了,現(xiàn)在你店里賣iPhone X携冤,價格6666闲勺,商品id為8001菜循。
關(guān)于8001這個商品后端只會給你返回這個信息。
可是這個時候老板說子眶,我們要增加一件商品,名字叫做 蘋果10 粤咪, 但實際上就是iPhone X渴杆。
前端展示兩個商品磁奖,但是實際上就是同一個,因為goodsId只能有一個冠跷。這個時候身诺,你就需要手動copy一個對象霉赡,然后修改他的商品名了。
(例子嘛蜂挪,只是例子嗓化,莫認真严肪,大概說明情況即可)
一隅津、關(guān)于 = 的賦值伦仍,引用數(shù)據(jù)類型是地址傳遞
我們知道,Java的數(shù)據(jù)分為
- 基本數(shù)據(jù)類型
- 引用數(shù)據(jù)類型隧枫。
通常,我們會用 = 做賦值操作协怒。
在基本數(shù)據(jù)類型類型中孕暇,我們使用 = 做賦值操作赤兴,實際上就是做拷貝操作桶良,兩個變量對應兩個地址。
在引用類型中曲秉,我們使用 = 號做賦值疲牵,只是執(zhí)行值傳遞瑰步,兩個對象對應同一個地址
璧眠。
public class AClass {
public static void main(String[] args) {
int i1 = 3;
int i2 = 5;
i2 = 6;
System.out.println("i1:"+i1);
System.out.println("i2:"+i2);
System.out.println("========");
Phone p1 = new Phone();
p1.size = 5;
Phone p2 = new Phone();
p2 = p1;
p2.size = 6;
System.out.println("p1:"+p1.size);
System.out.println("p2:"+p2.size);
}
}
public class Phone {
public int size;
}
.
.
Console:
i1:3
i2:6
========
p1:6
p2:6
可見责静,在
p2=p1這個過程中灾螃,執(zhí)行了是地址傳遞,兩個對象指向同一個地址嵌赠,導致修改了p2的屬性值也同時影響p1的屬性值熄赡。
關(guān)于這點彼硫,大家都非常熟悉了凌箕。
顯然牵舱,= 操作無法滿足我們的需求缺虐,我們要的是對象拷貝高氮。
在很多語言中,對象拷貝都是分為 淺拷貝
和 深拷貝
的腰涧。
二紊浩、淺拷貝和深拷貝
大體區(qū)分
淺拷貝:
對基本數(shù)據(jù)類型進行值傳遞坊谁,對引用數(shù)據(jù)類型進行引用傳遞般的拷貝口芍,此為淺拷貝。
深拷貝:
對基本數(shù)據(jù)類型進行值傳遞颠猴,對引用數(shù)據(jù)類型小染,創(chuàng)建一個新的對象裤翩,并復制其內(nèi)容踊赠,此為深拷貝。
實現(xiàn)拷貝方式
-
1今穿、通過set方法荣赶,逐一賦值
(當對象內(nèi)部復雜時,這種要是很要命利诺,特別是每次修改屬性還要聯(lián)動修改) 2剩燥、通過重寫java.lang.Object類中的方法clone()
3灭红、通過序列化的方式實現(xiàn)對象的拷貝变擒。
-
4、通過org.apache.commons中的工具類BeanUtils和PropertyUtils等進行對象復制
(類似PropertyUtils的工具有很多策添,但是他們幾乎只在在基于JDK的環(huán)境中用毫缆,安卓用不了 )
clone方法實現(xiàn)淺拷貝
不管是淺拷貝還是深拷貝苦丁,我們都可以利用萬類之總 Object 里的 clone()方法來實現(xiàn)旺拉。
淺拷貝
對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型進行引用傳遞般的拷貝蒂秘,此為淺拷貝。
實現(xiàn)淺拷貝的步驟
1规丽、實現(xiàn)Cloneable接口
2赌莺、復寫clone方法艘狭,并 return super.clone()
public class Phone{
public String goodsName;
public double price;
public int goodsId ;
}
class Person implements Cloneable{ // 淺拷貝 step1
public String perName;
public int age;
public Phone phone;
@Override
public Object clone() throws CloneNotSupportedException
{
return super.clone();// 淺拷貝 step2
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "info : goodsName:"+perName+"\n"+
"age:"+age+"\n"+
"phone.goodsName:"+phone.goodsName+"\n"+
"phone.price:"+phone.price+"\n"+
"phone.goodsId:"+phone.goodsId+"\n"
;
}
}
.
.
public class AClass {
public static void main(String[] args) {
Phone p1 = new Phone();
p1.goodsName = "iPhone X";
p1.goodsId = 8001;
p1.price = 666;
Person person = new Person();
person.perName="張三";
person.age = 18;
person.phone = p1;
Person person2 = null;
try {
person2 = (Person) person.clone();
// 淺拷貝后修改值
person2.perName= "李四";
person2.age= 20;
person2.phone.goodsId= 9001;
person2.phone.goodsName= "MIX 3";
person2.phone.price= 3299;
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("person:"+person.toString());
System.out.println("person2:"+person2.toString());
}
}
.
.
console
person:info : goodsName:張三
age:18
phone.goodsName:MIX 3
phone.price:3299.0
phone.goodsId:9001
person2:info : goodsName:李四
age:20
phone.goodsName:MIX 3
phone.price:3299.0
phone.goodsId:9001
可見遵倦,淺拷貝中:
- 如果原型對象的成員變量是值類型梧躺,將復制一份給克隆對象。
- 如果原型對象的成員變量是引用類型巩踏,只是進行值地址的傳遞塞琼,原型對象和克隆對象的成員變量指向相同的內(nèi)存地址禁舷,所以克隆對象修改引用類型的數(shù)據(jù),原型對象會也會跟著改變在讶。
clone方法實現(xiàn)深拷貝
深拷貝
對基本數(shù)據(jù)類型進行值傳遞构哺,對引用數(shù)據(jù)類型战坤,創(chuàng)建一個新的對象,并復制其內(nèi)容碟嘴,此為深拷貝娜扇。
實現(xiàn)深拷貝的步驟
1栅组、實現(xiàn)Cloneable接口
2、原型對象的值類型內(nèi)部也實現(xiàn)Cloneable接口和對應復寫clone()
3玉掸、復寫clone方法
4刃麸、把引用的對象也進行可控并進行返回
其實微調(diào)一下代碼,就實現(xiàn)了 深拷貝司浪。
(需要改動的只有這一份)
public class Phone implements Cloneable{ // 深拷貝 step2
public String goodsName;
public double price;
public int goodsId ;
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
class Person implements Cloneable{ // 深拷貝 step1
public String perName;
public int age;
public Phone phone;
@Override
public Object clone() throws CloneNotSupportedException
{
//return super.clone();
// 深拷貝 step3
Person person = (Person) super.clone();
// 深拷貝 step4 把 值類型 的成員變量也進行拷貝
person.phone = ((Phone) (person.phone.clone()));
return person;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "info : goodsName:"+perName+"\n"+
"age:"+age+"\n"+
"phone.goodsName:"+phone.goodsName+"\n"+
"phone.price:"+phone.price+"\n"+
"phone.goodsId:"+phone.goodsId+"\n"
;
}
}
.
.
console:
person:info : goodsName:張三
age:18
phone.goodsName:iPhone X
phone.price:666.0
phone.goodsId:8001
person2:info : goodsName:李四
age:20
phone.goodsName:MIX 3
phone.price:3299.0
phone.goodsId:9001
可見泊业,改為深拷貝之后把沼。
楚河漢界,各不相犯吁伺。你我各自獨立智政。
可是利用clone的方式實現(xiàn)的深度拷貝,實在太麻煩续捂。
比如我們Bean里面各種嵌套,原型對象的引用類型里面還有引用類型宦搬,嵌套四五層牙瓢。
那么寫這些clone也是夠嗆的。
三间校、利用Serializable和Parcelable實現(xiàn)深拷貝
用序列化的方式實現(xiàn)深拷貝
實現(xiàn)Serializable接口矾克,通過對象的序列化和反序列化實現(xiàn)克隆,可以實現(xiàn)深度克隆憔足。
如果是Android開發(fā)胁附,自然還可以用Parcelable序列化的方式實現(xiàn)實現(xiàn)深拷貝
Serializable深拷貝
.
.
public class Phone implements Serializable{
private static final long serialVersionUID = -6844928160614375642L;
public String goodsName;
public double price;
public int goodsId ;
}
class Person implements Serializable{
private static final long serialVersionUID = 2254270518697430558L;
public String perName;
public int age;
public Phone phone;
@Override
public String toString() {
// TODO Auto-generated method stub
return "info : goodsName:"+perName+"\n"+
"age:"+age+"\n"+
"phone.goodsName:"+phone.goodsName+"\n"+
"phone.price:"+phone.price+"\n"+
"phone.goodsId:"+phone.goodsId+"\n"
;
}
}
.
.
public class AClass {
public static void main(String[] args) {
Phone p1 = new Phone();
p1.goodsName = "iPhone X";
p1.goodsId = 8001;
p1.price = 666;
Person person = new Person();
person.perName="張三";
person.age = 18;
person.phone = p1;
Person person2 = null;
try {
person2 = CloneUtil.clone(person);
// 淺拷貝后修改值
person2.perName= "李四";
person2.age= 20;
person2.phone.goodsId= 9001;
person2.phone.goodsName= "MIX 3";
person2.phone.price= 3299;
} catch (ClassNotFoundException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("person:"+person.toString());
System.out.println("person2:"+person2.toString());
}
}
.
.
CloneUtil
public class CloneUtil {
private CloneUtil() {
throw new AssertionError();
}
public static <T extends Serializable> T clone(T object) throws IOException,
ClassNotFoundException {
// 說明:調(diào)用ByteArrayOutputStream或ByteArrayInputStream對象的close方法沒有任何意義
// 這兩個基于內(nèi)存的流只要垃圾回收器清理對象就能夠釋放資源,這一點不同于對外資源(如文件流)的釋放
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (T) ois.readObject();
}
}
.
.
console
person:info : goodsName:張三
age:18
phone.goodsName:iPhone X
phone.price:666.0
phone.goodsId:8001
person2:info : goodsName:李四
age:20
phone.goodsName:MIX 3
phone.price:3299.0
phone.goodsId:9001
可見滓彰,依然深拷貝控妻。
Parcelable 深拷貝
利用安卓特有的Parcelable序列化方式,也可以進行深拷貝揭绑。
示例
public class Person implements Parcelable {
public String perName;
public int age;
public Phone phone;
public Person() {
}
protected Person(Parcel in) {
perName = in.readString();
age = in.readInt();
phone = in.readParcelable(Phone.class.getClassLoader());
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public String toString() {
// TODO Auto-generated method stub
return "info : goodsName:"+perName+"\n"+
"age:"+age+"\n"+
"phone.goodsName:"+phone.goodsName+"\n"+
"phone.price:"+phone.price+"\n"+
"phone.goodsId:"+phone.goodsId+"\n"
;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(perName);
parcel.writeInt(age);
parcel.writeParcelable(phone, i);
}
}
.
.
public class Phone implements Parcelable {
public String goodsName;
public double price;
public int goodsId ;
public Phone() {
}
public Phone(Parcel in) {
goodsName = in.readString();
price = in.readDouble();
goodsId = in.readInt();
}
public static final Creator<Phone> CREATOR = new Creator<Phone>() {
@Override
public Phone createFromParcel(Parcel in) {
return new Phone(in);
}
@Override
public Phone[] newArray(int size) {
return new Phone[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(goodsName);
parcel.writeDouble(price);
parcel.writeInt(goodsId);
}
}
.
.
public class ParcelHelper {
public static <T> T copy(Parcelable input) {
Parcel parcel = null;
try {
parcel = Parcel.obtain();
parcel.writeParcelable(input, 0);
parcel.setDataPosition(0);
return parcel.readParcelable(input.getClass().getClassLoader());
} finally {
parcel.recycle();
}
}
}
.
.
進行拷貝和修改
Phone p1 = new Phone();
p1.goodsName = "iPhone X";
p1.goodsId = 8001;
p1.price = 666;
Person person = new Person();
person.perName="張三";
person.age = 18;
person.phone = p1;
Person person2 = null;
person2 = ParcelHelper.copy(person);
// 淺拷貝后修改值
person2.perName= "李四";
person2.age= 20;
person2.phone.goodsId= 9001;
person2.phone.goodsName= "MIX 3";
person2.phone.price= 3299;
System.out.println("person:"+person.toString());
System.out.println("person2:"+person2.toString());
.
.
console:
person:info : goodsName:張三
age:18
phone.goodsName:iPhone X
phone.price:666.0
phone.goodsId:8001
person2:info : goodsName:李四
age:20
phone.goodsName:MIX 3
phone.price:3299.0
phone.goodsId:9001
可見弓候,采用Parcelable的方式,依然可實現(xiàn)深拷貝他匪。
四菇存、 利用工具類庫進行深拷貝
除了clone和序列化接口。
我們還可以利用一些強大工具類庫來實現(xiàn)深度拷貝邦蜜。
- Apache
BeanUtil.CopyProperties
- apache
PropertyUtils.CopyProperties
- spring
BeanUtils.CopyProperties
- cglib
BeanCopier
- ezmorph
BeanMorpher
其中依鸥,BeanUtil最為常見,BeanCopier效率相對較高悼沈。
然后贱迟,在Java的世界你隨便耍。
在Adnroid的世界還是算了吧井辆。
這些類庫关筒,基本都是基于完整的JDK,而安卓的SDK對JDK進行了精簡杯缺,基本拜拜。
(文中的全部Bean沒有按照面向?qū)ο蟮姆庋b的思想進行g(shù)et和set睡榆,基本都是public萍肆,見諒)
關(guān)于工具類的袍榆,就不演示了。
本文完塘揣。