安卓/Java對象拷貝(淺/深拷貝、兩種序列化变汪、Beans等工具)

文/阿敏其人
本文出自阿敏其人簡書博客他嫡,轉(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
image.png

可見遵倦,淺拷貝中:

  • 如果原型對象的成員變量是值類型梧躺,將復制一份給克隆對象。
  • 如果原型對象的成員變量是引用類型巩踏,只是進行值地址的傳遞塞琼,原型對象和克隆對象的成員變量指向相同的內(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)于工具類的袍榆,就不演示了。
本文完塘揣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末包雀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亲铡,更是在濱河造成了極大的恐慌才写,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奖蔓,死亡現(xiàn)場離奇詭異赞草,居然都是意外死亡,警方通過查閱死者的電腦和手機吆鹤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門厨疙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疑务,你說我怎么就攤上這事沾凄。” “怎么了知允?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵撒蟀,是天一觀的道長。 經(jīng)常有香客問我温鸽,道長印蔗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任嗓蘑,我火速辦了婚禮沐飘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雹姊。我一直安慰自己股缸,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布吱雏。 她就那樣靜靜地躺著敦姻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歧杏。 梳的紋絲不亂的頭發(fā)上镰惦,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音犬绒,去河邊找鬼旺入。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的茵瘾。 我是一名探鬼主播礼华,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拗秘!你這毒婦竟也來了圣絮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雕旨,失蹤者是張志新(化名)和其女友劉穎扮匠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凡涩,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡棒搜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了突照。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帮非。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖讹蘑,靈堂內(nèi)的尸體忽然破棺而出末盔,到底是詐尸還是另有隱情,我是刑警寧澤座慰,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布陨舱,位于F島的核電站,受9級特大地震影響版仔,放射性物質(zhì)發(fā)生泄漏游盲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一蛮粮、第九天 我趴在偏房一處隱蔽的房頂上張望益缎。 院中可真熱鬧,春花似錦然想、人聲如沸莺奔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽令哟。三九已至,卻和暖如春妨蛹,著一層夾襖步出監(jiān)牢的瞬間屏富,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工蛙卤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狠半,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像典予,于是被迫代替她去往敵國和親甜滨。 傳聞我的和親對象是個殘疾皇子乐严,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359