安卓/Java物件拷貝(淺/深拷貝、兩種序列化、Beans等工具)
文/阿敏其人
本文出自阿敏其人簡書部落格,轉載請與本人聯絡。
為什麼要拷貝物件
我們幹嘛要去拷貝一個物件呢?
對於前端來說,這種情況多發於接收了後端的資料,但是在介面展示上資料不夠完整,後端不改資料,這時候就要你自己來動手拷貝物件了。
對應後端來說,多發於造資料給前端,為了配合前端。
乾巴巴的文字不好看,我來努力找個栗子吧。
假設你是個前端,做的是一個電商專案。每一個商品都有一個 名稱 ,價格 ,商品id。
然後,根據後端的返回,你知道需要如下一個bean。
public class Phone {
public String name;
public double price;
public int goodsId ;
}
問題來了,現在你店裡賣iPhone X,價格6666,商品id為8001。
關於8001這個商品後端只會給你返回這個資訊。
可是這個時候老闆說,我們要增加一件商品,名字叫做 蘋果10 , 但實際上就是iPhone X。
前端展示兩個商品,但是實際上就是同一個,因為goodsId只能有一個。這個時候,你就需要手動copy一個物件,然後修改他的商品名了。
(例子嘛,只是例子,莫認真,大概說明情況即可)
一、關於 = 的賦值,引用資料型別是地址傳遞
我們知道,Java的資料分為
- 基本資料型別
- 引用資料型別。
通常,我們會用 = 做賦值操作。
在基本資料型別型別中,我們使用 = 做賦值操作,實際上就是做拷貝操作,兩個變數對應兩個地址。
在引用型別中,我們使用 = 號做賦值,只是執行值傳遞,兩個物件對應同一個地址
。
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這個過程中,執行了是地址傳遞,兩個物件指向同一個地址,導致修改了p2的屬性值也同時影響p1的屬性值。
關於這點,大家都非常熟悉了。
顯然,= 操作無法滿足我們的需求,我們要的是物件拷貝。
在很多語言中,物件拷貝都是分為 淺拷貝
和 深拷貝
的。
二、淺拷貝和深拷貝
大體區分
淺拷貝:對基本資料型別進行值傳遞,對引用資料型別進行引用傳遞般的拷貝,此為淺拷貝。
深拷貝:對基本資料型別進行值傳遞,對引用資料型別,建立一個新的物件,並複製其內容,此為深拷貝。
實現拷貝方式
-
1、通過set方法,逐一賦值
(當物件內部複雜時,這種要是很要命,特別是每次修改屬性還要聯動修改) 2、通過重寫java.lang.Object類中的方法clone()
3、通過序列化的方式實現物件的拷貝。
-
4、通過org.apache.commons中的工具類BeanUtils和PropertyUtils等進行物件複製
(類似PropertyUtils的工具有很多,但是他們幾乎只在在基於JDK的環境中用,安卓用不了 )
clone方法實現淺拷貝
不管是淺拷貝還是深拷貝,我們都可以利用萬類之總 Object 裡的 clone()方法來實現。
淺拷貝
對基本資料型別進行值傳遞,對引用資料型別進行引用傳遞般的拷貝,此為淺拷貝。
實現淺拷貝的步驟
1、實現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
可見,淺拷貝中:
- 如果原型物件的成員變數是值型別,將複製一份給克隆物件。
- 如果原型物件的成員變數是引用型別,只是進行值地址的傳遞,原型物件和克隆物件的成員變數指向相同的記憶體地址,所以克隆物件修改引用型別的資料,原型物件會也會跟著改變。
clone方法實現深拷貝
深拷貝
對基本資料型別進行值傳遞,對引用資料型別,建立一個新的物件,並複製其內容,此為深拷貝。
實現深拷貝的步驟
1、實現Cloneable介面
2、原型物件的值型別內部也實現Cloneable介面和對應複寫clone()
3、複寫clone方法
4、把引用的物件也進行可控並進行返回
其實微調一下程式碼,就實現了 深拷貝。
(需要改動的只有這一份)
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的方式實現的深度拷貝,實在太麻煩。
比如我們Bean裡面各種巢狀,原型物件的引用型別裡面還有引用型別,巢狀四五層。
那麼寫這些clone也是夠嗆的。
三、利用Serializable和Parcelable實現深拷貝
用序列化的方式實現深拷貝
實現Serializable介面,通過物件的序列化和反序列化實現克隆,可以實現深度克隆。
如果是Android開發,自然還可以用Parcelable序列化的方式實現實現深拷貝
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 {
// 說明:呼叫ByteArrayOutputStream或ByteArrayInputStream物件的close方法沒有任何意義
// 這兩個基於記憶體的流只要垃圾回收器清理物件就能夠釋放資源,這一點不同於對外資源(如檔案流)的釋放
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的方式,依然可實現深拷貝。
四、 利用工具類庫進行深拷貝
除了clone和序列化介面。
我們還可以利用一些強大工具類庫來實現深度拷貝。
- Apache
BeanUtil.CopyProperties
- apache
PropertyUtils.CopyProperties
- spring
BeanUtils.CopyProperties
- cglib
BeanCopier
- ezmorph
BeanMorpher
其中,BeanUtil最為常見,BeanCopier效率相對較高。
然後,在Java的世界你隨便耍。
在Adnroid的世界還是算了吧。
這些類庫,基本都是基於完整的JDK,而安卓的SDK對JDK進行了精簡,基本拜拜。
(文中的全部Bean沒有按照物件導向的封裝的思想進行get和set,基本都是public,見諒)
關於工具類的,就不演示了。
本文完。
相關文章
- 物件深拷貝和淺拷貝物件
- 淺談深拷貝與淺拷貝?深拷貝幾種方法。
- 實現物件淺拷貝、深拷貝物件
- 聊聊物件深拷貝和淺拷貝物件
- Java深拷貝和淺拷貝Java
- 淺拷貝&深拷貝
- 【JavaScript】物件的淺拷貝與深拷貝JavaScript物件
- jquery之物件拷貝深拷貝淺拷貝案例講解jQuery物件
- java深克隆(深拷貝)和淺克隆(淺拷貝)Java
- 淺拷貝與深拷貝
- 淺拷貝和深拷貝
- 深拷貝和淺拷貝
- JavaScript物件的深拷貝以及淺拷貝分析JavaScript物件
- 一文搞懂Java引用拷貝、淺拷貝、深拷貝Java
- 【JS】深拷貝與淺拷貝,實現深拷貝的幾種方法JS
- PHP 物件導向 - 物件的淺拷貝與深拷貝PHP物件
- 淺談Java中的淺拷貝和深拷貝Java
- Python淺拷貝與深拷貝Python
- JavaScript深拷貝和淺拷貝JavaScript
- javascript 淺拷貝VS深拷貝JavaScript
- js 淺拷貝和深拷貝JS
- JS深拷貝與淺拷貝JS
- iOS深拷貝和淺拷貝iOS
- python深拷貝與淺拷貝Python
- js 深拷貝和淺拷貝JS
- JavaScript淺拷貝和深拷貝JavaScript
- python 指標拷貝,淺拷貝和深拷貝Python指標
- 【Java】3-淺拷貝/ 2-深拷貝Java
- Java 輕鬆理解深拷貝與淺拷貝Java
- 深入淺出深拷貝與淺拷貝
- 淺探js深拷貝和淺拷貝JS
- 正則以及淺拷貝深拷貝
- 談談深拷貝與淺拷貝
- 賦值、淺拷貝與深拷貝賦值
- JavaScript之深拷貝和淺拷貝JavaScript
- ECMAScript-淺拷貝和深拷貝
- C++淺拷貝和深拷貝C++
- 深拷貝、淺拷貝與Cloneable介面