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
附錄beanUtils底層原始碼實現
class User {
private int id;
private String name;
private boolean demo = false;
public boolean isDemo() {
return demo;
}
public void setDemo(boolean demo) {
this.demo = demo;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class BeanUtils<T,K> {
public static void main(String args[])
{
User user = new User();
User user1 = new User();
user1.setDemo(false);
user.setName("12");
user.setDemo(true);
user.setId(1);
user1.setName("21");
user1.setId(2);
BeanUtils<User,User> beanUtils = new BeanUtils<User,User>();
Set<String> stringSet = new HashSet<String>();
stringSet.add("id"); //設定不進行跟新的屬性名稱
System.out.println(user.getId());
System.out.println(user.getName());
System.out.println(user.isDemo());
try {
beanUtils.copyProperties(user1,user,stringSet);
System.out.println(user.getId());
System.out.println(user.getName());
System.out.println(user.isDemo());
} catch (InvocationTargetException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (IllegalAccessException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
/**
*
* t 源類
* k 需要進行屬性更新的類
* */
public void copyProperties(T t,K k,Set<String> stringSet) throws InvocationTargetException, IllegalAccessException {
Class clazz = t.getClass();
Class target = k.getClass();
Field[] fields = clazz.getDeclaredFields(); //獲取源類的所用欄位
if (stringSet==null)
{
stringSet = new HashSet<String>();
}
for (Field field : fields) //遍歷欄位
{
String fieldName = field.getName(); //獲取名稱
if (stringSet.contains(fieldName)) //通過這個動態的指定需要更新的欄位
continue;
Field targetField = null;
try {
targetField = target.getDeclaredField(fieldName); //根據源類的屬性獲取目標類的屬性
} catch (NoSuchFieldException e) {
continue;
}
if (field.getType()==targetField.getType())
{
String getMethodName = null;
String setMethodName = null;
if (field.getType().getName()=="boolean")
{
getMethodName = "is"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
setMethodName = "set" + fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
} else
{
getMethodName = "get"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
setMethodName = "set" + fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
}
Method getMethod = null; //源類的get方法
Method setMethod = null; //目標類的方法
try {
getMethod = clazz.getDeclaredMethod(getMethodName);
setMethod = target.getDeclaredMethod(setMethodName,field.getType());
Object value = getMethod.invoke(t); //通過get方法獲得值
setMethod.invoke(k,value);
} catch (NoSuchMethodException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
}
}
//針對於list屬性
public List<K> copyListProperties(List<T> list,Class clazz,List<K> newList,Set<String> stringSet)
{
Method[] methods = clazz.getMethods();
if(list==null)
return null;
for(T t : list)
{
Field[] fields = clazz.getDeclaredFields();
K object = null;
try {
object = (K)clazz.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
for(Field field : fields)
{
String newFieldName = field.getName();
if (stringSet.contains(newFieldName))
continue;
try {
Field oldField = t.getClass().getDeclaredField(field.getName());
} catch (NoSuchFieldException e) {
System.out.println(e.getMessage());
continue;
}
for(Method method : methods)
{
if(Modifier.isPublic(method.getModifiers()))//如果是公共的方法
{
String method_name = method.getName();
if(method_name.startsWith("set"))
{
String setMethodName = method.getName().substring(3).toLowerCase();
if(setMethodName.equals(newFieldName.toLowerCase()))
{
String getMethodName = "";
getMethodName = method.getName().substring(3);
String getName = "get"+getMethodName;
try {
method.invoke(object,t.getClass().getMethod(getName).invoke(t));
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
getMethodName = "is" + method.getName().substring(3);
try {
method.invoke(object,t.getClass().getMethod(getMethodName).invoke(t));
} catch (NoSuchMethodException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (InvocationTargetException e1) {
e1.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (IllegalAccessException e1) {
e1.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
} catch (InvocationTargetException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (IllegalAccessException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
}
}
}
}
newList.add(object);
}
return newList;
}
基於註解的
<code class="language-java">/**
* Created with IntelliJ IDEA.
* User: K
* Date: 13-11-11
* Time: 下午3:24
* To change this template use File | Settings | File Templates.
*/
//用來說明這個類的描述範圍
/* ElementType取值
1.CONSTRUCTOR:用於描述構造器
2.FIELD:用於描述域
3.LOCAL_VARIABLE:用於描述區域性變數
4.METHOD:用於描述方法
5.PACKAGE:用於描述包
6.PARAMETER:用於描述引數
7.TYPE:用於描述類、介面(包括註解型別) 或enum宣告 */
/* RetentionPolicy取值
1.SOURCE:在原始檔中有效(即原始檔保留)
2.CLASS:在class檔案中有效(即class保留)
3.RUNTIME:在執行時有效(即執行時保留) */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) //指定生命週期
@Documented
public @interface IgnoreProperty {
}
</code>
<code class="language-java">public class BeanUtils<T,K> {
public static void main(String args[])
{
BeanUtils<User,User> beanUtils = new BeanUtils<User,User>();
User user = new User();
User user1 = new User();
user.setId(1);
user.setDemo(false);
user.setName("w");
user1.setId(2);
user1.setDemo(true);
user1.setName("k");
System.out.println(user1.getId());
System.out.println(user1.getName());
System.out.println(user1.isDemo());
beanUtils.copyPropertiesAnnoations(user,user1);
System.out.println(user1.getId());
System.out.println(user1.getName());
System.out.println(user1.isDemo());
}
public void copyPropertiesAnnoations(T t,K k)
{
Class clazz = t.getClass();
Class target = k.getClass();
Field[] fields = clazz.getDeclaredFields(); //獲取源類的所用欄位
for (Field field : fields) //遍歷欄位
{
Annotation annotations[] = field.getDeclaredAnnotations();//獲取到這個欄位上的註解
for (Annotation annotation : annotations)
{
String ann = "@com.ceit.cnivi.permission.util.bean.IgnoreProperty()";//這個包名是基於自己的註解的包名去寫的 大家改成自己的就行了
if (ann.equals(annotation.toString()))
{
return;
}
}
String fieldName = field.getName(); //獲取名稱
Field targetField = null;
try {
targetField = target.getDeclaredField(fieldName); //根據源類的屬性獲取目標類的屬性
} catch (NoSuchFieldException e) {
continue;
}
if (field.getType()==targetField.getType())
{
String getMethodName = null;
String setMethodName = null;
if (field.getType().getName()=="boolean")
{
getMethodName = "is"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
setMethodName = "set" + fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
} else
{
getMethodName = "get"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
setMethodName = "set" + fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
}
Method getMethod = null; //源類的get方法
Method setMethod = null; //目標類的方法
try {
getMethod = clazz.getDeclaredMethod(getMethodName);
setMethod = target.getDeclaredMethod(setMethodName,field.getType());
Object value = getMethod.invoke(t); //通過get方法獲得值
setMethod.invoke(k,value);
} catch (NoSuchMethodException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (InvocationTargetException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (IllegalAccessException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
}
}
}</code>
注意這個地方 得改成自己自定義註解所在的地方@com.ceit.cnivi.permission.util.bean.IgnoreProperty()
程式碼和之前的也沒有什麼太大的變化,只是加了一個註解的實現而已,基於註解那個採用的是字串的比較
Clone 方法
Java 的深拷貝和淺拷貝,其實現方式正是通過呼叫 Object 類的 clone() 方法來完成。在 Object.class 類中,原始碼為:
protected native Object clone() throws CloneNotSupportedException;
用 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,接著列印這兩個物件的內容。
程式碼中我們只是更改了克隆物件 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();
}
因為序列化產生的是兩個完全獨立的物件,所有無論巢狀多少個引用型別,序列化都是能實現深拷貝的。