偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

簡(jiǎn)單聊聊對(duì)象淺拷貝和深拷貝,真不簡(jiǎn)單!

開(kāi)發(fā) 前端
本文主要圍繞對(duì)象的淺拷貝和深拷貝,從使用方面做了一次簡(jiǎn)單的內(nèi)容總結(jié)。淺拷貝下,原對(duì)象和目標(biāo)對(duì)象,引用都是同一個(gè)對(duì)象,當(dāng)被引用的對(duì)象數(shù)據(jù)發(fā)生變化時(shí),相關(guān)的引用者也會(huì)跟著一起變。

一、摘要

上篇文章中,我們有介紹到對(duì)象屬性復(fù)制相關(guān)的工具,這些工具所進(jìn)行的對(duì)象拷貝,其實(shí)都是淺拷貝模式。

可能有的同學(xué)會(huì)發(fā)出疑問(wèn),什么叫淺拷貝?

我們都知道,Java 中的數(shù)據(jù)類(lèi)型分為值類(lèi)型(基本數(shù)據(jù)類(lèi)型)和引用類(lèi)型,值類(lèi)型包括 byte、short、 int、long、float、double、boolean、char 等簡(jiǎn)單數(shù)據(jù)類(lèi)型,引用類(lèi)型包括類(lèi)、接口、數(shù)組等復(fù)雜類(lèi)型。

根據(jù)數(shù)據(jù)類(lèi)型的不同,在進(jìn)行屬性值拷貝的時(shí)候,如果是值類(lèi)型,復(fù)制的是屬性值,如果是復(fù)雜類(lèi)型,比如對(duì)象,復(fù)制的內(nèi)容可能是屬性對(duì)應(yīng)的內(nèi)存引用地址。

因此,在 Java 中對(duì)于復(fù)雜類(lèi)型的數(shù)據(jù),也分為**淺拷貝(淺克隆)與深拷貝(深克隆)**方式,區(qū)別如下:

  • 淺拷貝:將原對(duì)象或原數(shù)組的引用直接賦給新對(duì)象或者新數(shù)組,新對(duì)象只是原對(duì)象的一個(gè)引用,也就是說(shuō)不管新對(duì)象還是原對(duì)象,都是引用同一個(gè)對(duì)象
  • 深拷貝:創(chuàng)建一個(gè)新的對(duì)象或者數(shù)組,將原對(duì)象的各項(xiàng)屬性的值拷貝過(guò)來(lái),是“值”而不是“引用”,兩者對(duì)象是不一樣的

對(duì)于概念的解釋?zhuān)赡芤埠茈y理解,下面我們簡(jiǎn)單的通過(guò)幾個(gè)案例向大家介紹!

二、案例實(shí)踐

2.1、淺拷貝

首先我們新建兩個(gè)對(duì)象,其中User關(guān)聯(lián)Account對(duì)象,內(nèi)容如下:

public class User {

    /**
     * 用戶(hù)ID
     */
    private Long userId;

    /**
     * 賬戶(hù)信息
     */
    private Account account;

    //...get、set

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", account=" + account +
                '}';
    }
}
public class Account {

    /**
     * 賬號(hào)余額
     */
    private BigDecimal money;

    //...get、set

    @Override
    public String toString() {
        return "Account{" +
                "money=" + money +
                '}';
    }
}

使用Spring BeanUtils工具進(jìn)行對(duì)象屬性復(fù)制,操作如下:

// 定義某用戶(hù),賬戶(hù)余額 100塊
Account sourceAccount = new Account();
sourceAccount.setMoney(BigDecimal.valueOf(100));

User sourceUser = new User();
sourceUser.setUserId(1L);
sourceUser.setAccount(sourceAccount);

// 進(jìn)行對(duì)象屬性拷貝
User targetUser = new User();
BeanUtils.copyProperties(sourceUser, targetUser);
System.out.println("修改嵌套對(duì)象屬性值前的結(jié)果:" + targetUser.toString());

//修改原始對(duì)象賬戶(hù)余額為200
sourceAccount.setMoney(BigDecimal.valueOf(200));

System.out.println("修改嵌套對(duì)象屬性值后的結(jié)果:" + targetUser.toString());

輸出結(jié)果如下:

修改嵌套對(duì)象屬性值前的結(jié)果:User{userId=1, account=Account{money=100}}
修改嵌套對(duì)象屬性值后的結(jié)果:User{userId=1, account=Account{money=200}}

從結(jié)果上可以很明顯的得出結(jié)論:當(dāng)修改原始的嵌套對(duì)象Account的屬性值時(shí),目標(biāo)對(duì)象的Account對(duì)象對(duì)應(yīng)的值也跟著發(fā)生變化。

很顯然,這與我們預(yù)期想要的對(duì)象屬性拷貝是想違背的,我們所期待的結(jié)果是:原始對(duì)象值即使發(fā)生變化,目標(biāo)對(duì)象的值也不應(yīng)該發(fā)生變化!

面對(duì)這種情況,怎么處理呢?

我們可以把對(duì)象Account單獨(dú)拉出來(lái),進(jìn)行一次屬性值拷貝,然后再進(jìn)行封裝,比如操作如下:

// 定義某用戶(hù),賬戶(hù)余額 100塊
Account sourceAccount = new Account();
sourceAccount.setMoney(BigDecimal.valueOf(100));

User sourceUser = new User();
sourceUser.setUserId(1L);
sourceUser.setAccount(sourceAccount);


// 拷貝 Account 對(duì)象
Account targetAccount = new Account();
BeanUtils.copyProperties(sourceAccount, targetAccount);

// 拷貝 User 對(duì)象
User targetUser = new User();
BeanUtils.copyProperties(sourceUser, targetUser);
targetUser.setAccount(targetAccount);
System.out.println("修改嵌套對(duì)象屬性值前的結(jié)果:" + targetUser.toString());

//修改原始對(duì)象賬戶(hù)余額為200
sourceAccount.setMoney(BigDecimal.valueOf(200));

System.out.println("修改嵌套對(duì)象屬性值后的結(jié)果:" + targetUser.toString());

輸出結(jié)果如下:

修改嵌套對(duì)象屬性值前的結(jié)果:User{userId=1, account=Account{money=100}}
修改嵌套對(duì)象屬性值后的結(jié)果:User{userId=1, account=Account{money=100}}

即使Account對(duì)象數(shù)據(jù)發(fā)生變化,也不會(huì)改目標(biāo)對(duì)象的數(shù)據(jù),與預(yù)期結(jié)果一致!

現(xiàn)在的情況是User只有一個(gè)嵌套對(duì)象Account,假如像這樣的對(duì)象有十幾個(gè)呢,采用以上方式顯然不可取。

這個(gè)時(shí)候深拷貝,該登場(chǎng)了!

2.2、深拷貝

Java 的深拷貝有兩種實(shí)現(xiàn)方式,第一種是通過(guò)將對(duì)象序列化到臨時(shí)文件,然后再通過(guò)反序列化方式,從臨時(shí)文件中讀取數(shù)據(jù),操作案例如下!

首先所有的類(lèi),必須實(shí)現(xiàn)Serializable接口,推薦顯式定義序列化 ID。

public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用戶(hù)ID
     */
    private Long userId;

    /**
     * 賬戶(hù)信息
     */
    private Account account;

    //...get、set

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", account=" + account +
                '}';
    }
}
public class Account implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 賬號(hào)余額
     */
    private BigDecimal money;

    //...get、set

    @Override
    public String toString() {
        return "Account{" +
                "money=" + money +
                '}';
    }
}
// 定義某用戶(hù),賬戶(hù)余額 100塊
Account sourceAccount = new Account();
sourceAccount.setMoney(BigDecimal.valueOf(100));

User sourceUser = new User();
sourceUser.setUserId(1L);
sourceUser.setAccount(sourceAccount);


//把對(duì)象寫(xiě)入文件中
try {
    FileOutputStream fos = new FileOutputStream("temp.out");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(sourceUser);
    oos.flush();
    oos.close();
} catch (IOException e) {
    e.printStackTrace();
}

//從文件中讀取對(duì)象
User targetUser = null;
try {
    FileInputStream fis = new FileInputStream("temp.out");
    ObjectInputStream ois = new ObjectInputStream(fis);
    targetUser = (User) ois.readObject();
    fis.close();
    ois.close();
}  catch (Exception e) {
    e.printStackTrace();
}

System.out.println("修改嵌套對(duì)象屬性值前的結(jié)果:" + targetUser.toString());

//修改原始對(duì)象賬戶(hù)余額為200
sourceAccount.setMoney(BigDecimal.valueOf(200));

System.out.println("修改嵌套對(duì)象屬性值后的結(jié)果:" + targetUser.toString());

輸出結(jié)果:

修改嵌套對(duì)象屬性值前的結(jié)果:User{userId=1, account=Account{money=100}}
修改嵌套對(duì)象屬性值后的結(jié)果:User{userId=1, account=Account{money=100}}

通過(guò)序列化和反序列化的方式,可以實(shí)現(xiàn)多層復(fù)雜的對(duì)象數(shù)據(jù)拷貝。

因?yàn)樯婕暗叫枰獙?shù)據(jù)寫(xiě)入臨時(shí)磁盤(pán),性能可能會(huì)有所下降!

2.3、json 序列化和反序列化

對(duì)于對(duì)象深度拷貝,還有第二種方式,那就是采用 json 序列化和反序列化相關(guān)的技術(shù)來(lái)實(shí)現(xiàn),同時(shí)性能也比將數(shù)據(jù)寫(xiě)入臨時(shí)磁盤(pán)的方式要好很多,并且不需要顯式實(shí)現(xiàn)序列化接口。

json 序列化和反序列化的底層思想是,將對(duì)象序列化成字符串;然后再將字符串通過(guò)反序列化方式成對(duì)象。

以jackson工具庫(kù)為例,具體使用方式如下!

首先導(dǎo)入相關(guān)的jackson依賴(lài)包!

<!--jackson依賴(lài)-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

其次,編寫(xiě)統(tǒng)一Json處理工具類(lèi)!

public class JsonUtil {

    private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);

    private static ObjectMapper objectMapper = new ObjectMapper();

    static {
        // 序列化時(shí),將對(duì)象的所有字段全部列入
        objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        // 允許沒(méi)有引號(hào)的字段名
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        // 自動(dòng)給字段名加上引號(hào)
        objectMapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, true);
        // 時(shí)間默認(rèn)以時(shí)間戳格式寫(xiě),默認(rèn)時(shí)間戳
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
        // 忽略空bean轉(zhuǎn)json的錯(cuò)誤
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        // 設(shè)置時(shí)間轉(zhuǎn)換所使用的默認(rèn)時(shí)區(qū)
        objectMapper.setTimeZone(TimeZone.getDefault());


        // 反序列化時(shí),忽略在json字符串中存在, 但在java對(duì)象中不存在對(duì)應(yīng)屬性的情況, 防止錯(cuò)誤
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);

        //序列化/反序列化,自定義設(shè)置
        SimpleModule module = new SimpleModule();
        // 序列化成json時(shí),將所有的long變成string
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        // 自定義參數(shù)配置注冊(cè)
        objectMapper.registerModule(module);
    }

    /**
     * 對(duì)象序列化成字符串
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> String objToStr(T obj) {
        if (null == obj) {
            return null;
        }

        try {
            return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            log.warn("objToStr error: ", e);
            return null;
        }
    }

    /**
     * 字符串反序列化成對(duì)象
     * @param str
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T strToObj(String str, Class<T> clazz) {
        try {
            return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
        } catch (Exception e) {
            log.warn("strToObj error: ", e);
            return null;
        }
    }

    /**
     * 字符串反序列化成對(duì)象(數(shù)組)
     * @param str
     * @param typeReference
     * @param <T>
     * @return
     */
    public static <T> T strToObj(String str, TypeReference<T> typeReference) {
        try {
            return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
        } catch (Exception e) {
            log.warn("strToObj error", e);
            return null;
        }
    }
}

最后,在相關(guān)的位置引入即可。

// 定義某用戶(hù),賬戶(hù)余額 100塊
Account sourceAccount = new Account();
sourceAccount.setMoney(BigDecimal.valueOf(100));

User sourceUser = new User();
sourceUser.setUserId(1L);
sourceUser.setAccount(sourceAccount);

// json序列化、反序列化
User targetUser = JsonUtil.strToObj(JsonUtil.objToStr(sourceUser), User.class);
System.out.println("修改嵌套對(duì)象屬性值前的結(jié)果:" + targetUser.toString());

//修改原始對(duì)象賬戶(hù)余額為200
sourceAccount.setMoney(BigDecimal.valueOf(200));

System.out.println("修改嵌套對(duì)象屬性值后的結(jié)果:" + targetUser.toString());

輸出結(jié)果:

修改嵌套對(duì)象屬性值前的結(jié)果:User{userId=1, account=Account{money=100}}
修改嵌套對(duì)象屬性值后的結(jié)果:User{userId=1, account=Account{money=100}}

與預(yù)期一致!

三、小結(jié)

本文主要圍繞對(duì)象的淺拷貝和深拷貝,從使用方面做了一次簡(jiǎn)單的內(nèi)容總結(jié)。

淺拷貝下,原對(duì)象和目標(biāo)對(duì)象,引用都是同一個(gè)對(duì)象,當(dāng)被引用的對(duì)象數(shù)據(jù)發(fā)生變化時(shí),相關(guān)的引用者也會(huì)跟著一起變。

深拷貝下,原對(duì)象和目標(biāo)對(duì)象數(shù)據(jù)是兩個(gè)完全獨(dú)立的存在,相互直接不受影響。

至于當(dāng)前對(duì)象數(shù)據(jù),是應(yīng)該走淺拷貝還是深拷貝模式好,完全取決于當(dāng)前業(yè)務(wù)的需求,沒(méi)有絕對(duì)的好或者不好!

如果當(dāng)前對(duì)象需要深拷貝,推薦采用 json 序列化和反序列化的方式實(shí)現(xiàn),相比通過(guò)文件寫(xiě)入的方式進(jìn)行序列化和反序列化,操作簡(jiǎn)單且性能高!

責(zé)任編輯:武曉燕 來(lái)源: Java極客技術(shù)
相關(guān)推薦

2021-10-19 08:20:47

單例模式設(shè)計(jì)模式面試

2014-02-24 14:45:23

XPath開(kāi)發(fā)工具

2020-12-16 07:36:46

Redis字符串數(shù)據(jù)

2017-12-25 15:35:36

iMac Pro芯片存儲(chǔ)

2021-07-16 12:33:24

Javascript深拷貝淺拷貝

2017-08-16 13:30:05

Java深拷貝淺拷貝

2021-09-27 11:07:11

深拷貝淺拷貝內(nèi)存

2022-07-26 08:07:03

Python淺拷貝深拷貝

2020-08-03 08:24:26

原型模式拷貝

2018-09-26 14:37:17

JavaScript前端編程語(yǔ)言

2021-01-08 06:15:09

深拷貝淺拷貝寫(xiě)時(shí)拷貝

2023-05-17 08:42:46

深拷貝Golang

2009-05-19 17:28:44

深拷貝淺拷貝clone()

2023-05-05 08:47:35

Java淺拷貝深拷貝

2020-10-12 08:35:22

JavaScript

2024-03-15 15:03:23

2025-04-27 09:45:58

JavaScript深拷貝淺拷貝

2024-08-02 08:43:24

JavaScript開(kāi)發(fā)者工具箱深拷貝

2022-09-30 15:03:09

C語(yǔ)言深拷貝淺拷貝

2018-05-10 14:20:18

前端JavaScript深拷貝
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)