日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747


一、摘要

上篇文章中,我們有介紹到對象屬性復制相關的工具,這些工具所進行的對象拷貝,其實都是淺拷貝模式。

可能有的同學會發出疑問,什么叫淺拷貝?

我們都知道,JAVA 中的數據類型分為值類型(基本數據類型)和引用類型,值類型包括 byte、short、 int、long、float、double、boolean、char 等簡單數據類型,引用類型包括類、接口、數組等復雜類型。

根據數據類型的不同,在進行屬性值拷貝的時候,如果是值類型,復制的是屬性值,如果是復雜類型,比如對象,復制的內容可能是屬性對應的內存引用地址。

因此,在 Java 中對于復雜類型的數據,也分為**淺拷貝(淺克隆)與深拷貝(深克隆)**方式,區別如下:

  • 淺拷貝:將原對象或原數組的引用直接賦給新對象或者新數組,新對象只是原對象的一個引用,也就是說不管新對象還是原對象,都是引用同一個對象
  • 深拷貝:創建一個新的對象或者數組,將原對象的各項屬性的值拷貝過來,是“值”而不是“引用”,兩者對象是不一樣的

對于概念的解釋,可能也很難理解,下面我們簡單的通過幾個案例向大家介紹!

二、案例實踐

2.1、淺拷貝

首先我們新建兩個對象,其中User關聯Account對象,內容如下:

public class User {

    /**
     * 用戶ID
     */
    private Long userId;

    /**
     * 賬戶信息
     */
    private Account account;

    //...get、set

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

    /**
     * 賬號余額
     */
    private BigDecimal money;

    //...get、set

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

使用Spring BeanUtils工具進行對象屬性復制,操作如下:

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

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

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

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

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

輸出結果如下:

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

從結果上可以很明顯的得出結論:當修改原始的嵌套對象Account的屬性值時,目標對象的Account對象對應的值也跟著發生變化。

很顯然,這與我們預期想要的對象屬性拷貝是想違背的,我們所期待的結果是:原始對象值即使發生變化,目標對象的值也不應該發生變化!

面對這種情況,怎么處理呢?

我們可以把對象Account單獨拉出來,進行一次屬性值拷貝,然后再進行封裝,比如操作如下:

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

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


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

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

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

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

輸出結果如下:

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

即使Account對象數據發生變化,也不會改目標對象的數據,與預期結果一致!

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

這個時候深拷貝,該登場了!

2.2、深拷貝

Java 的深拷貝有兩種實現方式,第一種是通過將對象序列化到臨時文件,然后再通過反序列化方式,從臨時文件中讀取數據,操作案例如下!

首先所有的類,必須實現Serializable接口,推薦顯式定義序列化 ID。

public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用戶ID
     */
    private Long userId;

    /**
     * 賬戶信息
     */
    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;

    /**
     * 賬號余額
     */
    private BigDecimal money;

    //...get、set

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

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

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


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

//從文件中讀取對象
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("修改嵌套對象屬性值前的結果:" + targetUser.toString());

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

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

輸出結果:

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

通過序列化和反序列化的方式,可以實現多層復雜的對象數據拷貝。

因為涉及到需要將數據寫入臨時磁盤,性能可能會有所下降!

2.3、json 序列化和反序列化

對于對象深度拷貝,還有第二種方式,那就是采用 json 序列化和反序列化相關的技術來實現,同時性能也比將數據寫入臨時磁盤的方式要好很多,并且不需要顯式實現序列化接口。

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

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

首先導入相關的jackson依賴包!

<!--jackson依賴-->
<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>

其次,編寫統一Json處理工具類!

public class JsonUtil {

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

    private static ObjectMApper objectMapper = new ObjectMapper();

    static {
        // 序列化時,將對象的所有字段全部列入
        objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        // 允許沒有引號的字段名
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        // 自動給字段名加上引號
        objectMapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, true);
        // 時間默認以時間戳格式寫,默認時間戳
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
        // 忽略空bean轉json的錯誤
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        // 設置時間轉換所使用的默認時區
        objectMapper.setTimeZone(TimeZone.getDefault());


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

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

    /**
     * 對象序列化成字符串
     * @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;
        }
    }

    /**
     * 字符串反序列化成對象
     * @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;
        }
    }

    /**
     * 字符串反序列化成對象(數組)
     * @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;
        }
    }
}

最后,在相關的位置引入即可。

// 定義某用戶,賬戶余額 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("修改嵌套對象屬性值前的結果:" + targetUser.toString());

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

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

輸出結果:

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

與預期一致!

三、小結

本文主要圍繞對象的淺拷貝和深拷貝,從使用方面做了一次簡單的內容總結。

淺拷貝下,原對象和目標對象,引用都是同一個對象,當被引用的對象數據發生變化時,相關的引用者也會跟著一起變。

深拷貝下,原對象和目標對象數據是兩個完全獨立的存在,相互直接不受影響。

至于當前對象數據,是應該走淺拷貝還是深拷貝模式好,完全取決于當前業務的需求,沒有絕對的好或者不好!

如果當前對象需要深拷貝,推薦采用 json 序列化和反序列化的方式實現,相比通過文件寫入的方式進行序列化和反序列化,操作簡單且性能高!

分享到:
標簽:拷貝
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定