1. 程式人生 > >java實現物件拷貝和屬性複製的使用

java實現物件拷貝和屬性複製的使用

java實現物件拷貝和屬性複製的使用

  對於某一屬性,即原始物件和目的物件的屬性名稱相同,就可以拷貝原始物件的屬性值到目的物件中。

  注意屬性必須新增set,get方法,否則拷貝不成功

  基本型別之間如果是屬性的型別不同,BeanUtils會嘗試去強制型別轉換,然後去拷貝,如果能轉換則不丟擲異常。所以在轉換時要確保屬性名稱相同,屬性型別最好也相同。

引入不同的包,jar中方法也不一樣

package org.springframework.beans;中的

     BeanUtils.copyProperties(A,B);

     是A中的值付給B

package org.springframework.beans;
BeanUtils.copyProperties(A,B);
   

package org.apache.commons.beanutils;(常用)

      BeanUtils.copyProperties(A,B);

      是B中的值付給A

 package org.apache.commons.beanutils;
 BeanUtils.copyProperties(aValue, aLocal)  

將alocal實體bean轉換為對應的avalue 物件:上面的程式碼從aLocal物件複製屬性到aValue物件。它相當簡單!它不管alocal(或對應的avalue)物件有多少個屬性,只管進行復制。我們假設 alocal物件有100個屬性。上面的程式碼使我們可以無需鍵入至少100行的冗長、容易出錯和反覆的get和set方法呼叫。

用法 

//得到TeacherForm   
TeacherForm teacherForm=(TeacherForm)form;   
  
//構造Teacher物件   
Teacher teacher=new Teacher();   
  
//賦值   
teacher.setName(teacherForm.getName());   
teacher.setAge(teacherForm.getAge());   
teacher.setGender(teacherForm.getGender());   
teacher.setMajor(teacherForm.getMajor());   
teacher.setDepartment(teacherForm.getDepartment());   
  
//持久化Teacher物件到資料庫   
HibernateDAO.save(teacher);   

而使用BeanUtils後,程式碼就大大改觀了,如下所示:

//得到TeacherForm   
TeacherForm teacherForm=(TeacherForm)form;   
  
//構造Teacher物件   
Teacher teacher=new Teacher();   
  
//賦值   
BeanUtils.copyProperties(teacher,teacherForm);   
  
//持久化Teacher物件到資料庫   
HibernateDAO.save(teacher); 

如果Teacher和TeacherForm間存在名稱不相同的屬性,則BeanUtils不對這些屬性進行處理,需要程式設計師手動處理。例如 Teacher包含modifyDate(該屬性記錄最後修改日期,不需要使用者在介面中輸入)屬性而TeacherForm無此屬性,那麼在上面程式碼的 copyProperties()後還要加上一句:

teacher.setModifyDate(new Date());  

 這裡要注意一點,java.util.Date是不被支援的,而它的子類java.sql.Date是被支援的。因此如果物件包含時間型別的屬性,且希望被轉換的時候,一定要使用java.sql.Date型別。否則在轉換時會提示argument mistype異常。

BeanUtils支援的轉換型別如下:

* java.lang.BigDecimal   
  
* java.lang.BigInteger   
  
* boolean and java.lang.Boolean   
  
* byte and java.lang.Byte   
  
* char and java.lang.Character   
  
* java.lang.Class   
  
* double and java.lang.Double   
  
* float and java.lang.Float   
  
* int and java.lang.Integer   
  
* long and java.lang.Long   
  
* short and java.lang.Short   
  
* java.lang.String   
  
* java.sql.Date   
  
* java.sql.Time   
  
* java.sql.Timestamp  

Clone 方法

 Java 的深拷貝和淺拷貝,其實現方式正是通過呼叫 Object 類的 clone() 方法來完成。在 Object.class 類中,原始碼為:

protected native Object clone() throws CloneNotSupportedException;

這是一個用 native 關鍵字修飾的方法,關於native關鍵字有一篇部落格專門有介紹,不理解也沒關係,只需要知道用 native 修飾的方法就是告訴作業系統,這個方法我不實現了,讓作業系統去實現。具體怎麼實現我們不需要了解,只需要知道 clone方法的作用就是複製物件,產生一個新的物件。

淺拷貝

package com.ys.test;

public class Person implements Cloneable{
    public String pname;
    public int page;
    public Address address;
    public Person() {}
    
    public Person(String pname,int page){
        this.pname = pname;
        this.page = page;
        this.address = new Address();
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    public void setAddress(String provices,String city ){
        address.setAddress(provices, city);
    }
    public void display(String name){
        System.out.println(name+":"+"pname=" + pname + ", page=" + page +","+ address);
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }
    
}
package com.ys.test;

public class Address {
    private String provices;
    private String city;
    public void setAddress(String provices,String city){
        this.provices = provices;
        this.city = city;
    }
    @Override
    public String toString() {
        return "Address [provices=" + provices + ", city=" + city + "]";
    }
    
}

這是一個我們要進行賦值的原始類 Person。下面我們產生一個 Person 物件,並呼叫其 clone 方法複製一個新的物件。

  注意:呼叫物件的 clone 方法,必須要讓類實現 Cloneable 介面,並且覆寫 clone 方法。

public void testShallowClone() throws Exception{
    Person p1 = new Person("zhangsan",21);
    p1.setAddress("湖北省", "武漢市");
    Person p2 = (Person) p1.clone();
    System.out.println("p1:"+p1);
    System.out.println("p1.getPname:"+p1.getPname().hashCode());
    
    System.out.println("p2:"+p2);
    System.out.println("p2.getPname:"+p2.getPname().hashCode());
    
    p1.display("p1");
    p2.display("p2");
    p2.setAddress("湖北省", "荊州市");
    System.out.println("將複製之後的物件地址修改:");
    p1.display("p1");
    p2.display("p2");
}

首先看原始類 Person 實現 Cloneable 介面,並且覆寫 clone 方法,它還有三個屬性,一個引用型別 String定義的 pname,一個基本型別 int定義的 page,還有一個引用型別 Address ,這是一個自定義類,這個類也包含兩個屬性 pprovices 和 city 。

  接著看測試內容,首先我們建立一個Person 類的物件 p1,其pname 為zhangsan,page為21,地址類 Address 兩個屬性為 湖北省和武漢市。接著我們呼叫 clone() 方法複製另一個物件 p2,接著列印這兩個物件的內容。

  從第 1 行和第 3 行列印結果:

  p1:[email protected]

  p2:[email protected]

  可以看出這是兩個不同的物件。

  從第 5 行和第 6 行列印的物件內容看,原物件 p1 和克隆出來的物件 p2 內容完全相同。

  程式碼中我們只是更改了克隆物件 p2 的屬性 Address 為湖北省荊州市(原物件 p1 是湖北省武漢市) ,但是從第 7 行和第 8 行列印結果來看,原物件 p1 和克隆物件 p2 的 Address 屬性都被修改了。

  也就是說物件 Person 的屬性 Address,經過 clone 之後,其實只是複製了其引用,他們指向的還是同一塊堆記憶體空間,當修改其中一個物件的屬性 Address,另一個也會跟著變化。

淺拷貝:建立一個新物件,然後將當前物件的非靜態欄位複製到該新物件,如果欄位是值型別的,那麼對該欄位執行復制;如果該欄位是引用型別的話,則複製引用但不復制引用的物件。因此,原始物件及其副本引用同一個物件。

深拷貝

深拷貝:建立一個新物件,然後將當前物件的非靜態欄位複製到該新物件,無論該欄位是值型別的還是引用型別,都複製獨立的一份。當你修改其中一個物件的任何內容時,都不會影響另一個物件的內容。

Object 類提供的 clone 是隻能實現 淺拷貝的。

如何實現深拷貝?

 ①、讓每個引用型別屬性內部都重寫clone() 方法

  既然引用型別不能實現深拷貝,那麼我們將每個引用型別都拆分為基本型別,分別進行淺拷貝。比如上面的例子,Person 類有一個引用型別 Address(其實String 也是引用型別,但是String型別有點特殊,後面會詳細講解),我們在 Address 類內部也重寫 clone 方法。如下:

 1 package com.ys.test;
 2 
 3 public class Address implements Cloneable{
 4     private String provices;
 5     private String city;
 6     public void setAddress(String provices,String city){
 7         this.provices = provices;
 8         this.city = city;
 9     }
10     @Override
11     public String toString() {
12         return "Address [provices=" + provices + ", city=" + city + "]";
13     }
14     @Override
15     protected Object clone() throws CloneNotSupportedException {
16         return super.clone();
17     }
18     
19 }

Person.class 的 clone() 方法:

1     @Override
2     protected Object clone() throws CloneNotSupportedException {
3         Person p = (Person) super.clone();
4         p.address = (Address) address.clone();
5         return p;
6     }

測試還是和上面一樣,我們會發現更改了p2物件的Address屬性,p1 物件的 Address 屬性並沒有變化。

  但是這種做法有個弊端,這裡我們Person 類只有一個 Address 引用型別,而 Address 類沒有,所以我們只用重寫 Address 類的clone 方法,但是如果 Address 類也存在一個引用型別,那麼我們也要重寫其clone 方法,這樣下去,有多少個引用型別,我們就要重寫多少次,如果存在很多引用型別,那麼程式碼量顯然會很大,所以這種方法不太合適。

//深度拷貝
public Object deepClone() throws Exception{
    // 序列化
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);

    oos.writeObject(this);

    // 反序列化
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);

    return ois.readObject();
}

因為序列化產生的是兩個完全獨立的物件,所有無論巢狀多少個引用型別,序列化都是能實現深拷貝的。