Java中把一個對象復制給另外一個對象引發的思考


  Spring生態在Java項目中被廣泛應用,從架構到技術應用再到常用的基本功能,Spring給我們的開發帶來了很大的便利。今天翻到項目中導出報表功能的時候,發現經常復制對象的方法:

  BeanUtils.copyProperties;

  把源對象的屬性值賦值給目標對象,Spring和Apache和其他的一些框架都給我們提供了對象屬性的拷貝方法:

  org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)

  org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)

  使用的時候注意這兩個方法的參數順序,源和目標是相反的。這兩個方法底層都用到了反射,心血來潮之下我自己造了一個對象拷貝的輪子,代碼如下:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;

public class ObjConvertor {
    private static  HashMap<String,HashMap<String,Method>> mapSetMethods=new  HashMap<String,HashMap<String,Method>>();
    private static HashMap<String,HashMap<String,Method>> mapGetMethods=new  HashMap<String,HashMap<String,Method>>();
    
    static
    {
        
    }
    
    public static boolean Convert(Object src,Object dst) {
        HashMap<String,Method> mapSrc=new HashMap<String,Method>();
        HashMap<String,Method> mapDst=new HashMap<String,Method>();
        if(mapGetMethods.containsKey(src.getClass().getTypeName()))
            mapSrc=mapGetMethods.get(src.getClass().getTypeName());
        else
        {
            Method srcMethods[]=src.getClass().getMethods();
            mapGetMethods.put(src.getClass().getTypeName(),mapSrc);
            for(Method method:srcMethods)
            {
                String name=method.getName();
                if(name.startsWith("get"))
                {
                    String prop=name.substring(3).toLowerCase();
                    mapSrc.put(prop, method);
                }
            }
        }
        if(mapSetMethods.containsKey(dst.getClass().getTypeName()))
            mapDst=mapSetMethods.get(dst.getClass().getTypeName());
        else
        {
            Method dstMethods[]=dst.getClass().getMethods();
            mapSetMethods.put(dst.getClass().getTypeName(),mapDst);
            for(Method method:dstMethods)
            {
                String name=method.getName();
                if(name.startsWith("set"))
                {
                    String prop=name.substring(3).toLowerCase();
                    mapDst.put(prop, method);
                }
            }
        }
        for(String prop:mapSrc.keySet())
        {
            if(mapDst.containsKey(prop)){
                Method setMethod=mapDst.get(prop);
                Method getMethod=mapSrc.get(prop);
                try {
                    Object data=getMethod.invoke(src);
                    if(data==null)
                        continue;
                    if(data instanceof java.sql.Time)
                    {
                        if(setMethod.getParameterTypes()[0]==String.class)
                            data=data.toString();
                    }
                    else if(data instanceof java.sql.Date)
                    {
                        if(setMethod.getParameterTypes()[0]==String.class)
                            data=new SimpleDateFormat("yyyy-MM-dd").format(data);
                    }
                    else if(data instanceof java.util.Date)
                    {
                        if(setMethod.getParameterTypes()[0]==String.class)
                            data=new SimpleDateFormat("yyyy-MM-dd").format((java.util.Date)data);
                    }
                    else if(data instanceof java.sql.Timestamp)
                    {
                        if(setMethod.getParameterTypes()[0]==String.class)
                            data=data.toString();
                    }
                    if(setMethod.getParameterTypes()[0]==java.sql.Time.class)
                    {
                        if(data instanceof String)
                            data=java.sql.Time.valueOf((String)data);
                    }
                    else if(setMethod.getParameterTypes()[0]==java.sql.Date.class)
                    {
                        if(data instanceof String)
                            data=java.sql.Date.valueOf((String)data);
                    }
                    else if(setMethod.getParameterTypes()[0]==java.util.Date.class)
                    {
                        if(data instanceof String)
                        {
                            data=(new SimpleDateFormat("yyyy-MM-dd")).parse((String)data);
                        }
                    }
                    else if(setMethod.getParameterTypes()[0]==java.sql.Timestamp.class)
                    {
                        if(data instanceof String)
                            data=java.sql.Timestamp.valueOf((String)data);
                    }
                    if(data==null)
                        continue;
                    setMethod.invoke(dst, data);
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    return false;
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    return false;
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    return false;
                } catch (ParseException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    return false;
                }
            }
        }
        return true;
    }
}

  對象拷貝的功能至此完結,如果項目中需要一些特殊格式的數據,可以考慮自己造輪子,這個就看業務需求了。

  接下來談談對象拷貝,如果在拷貝這個對象的時候,只對基本數據類型進行了拷貝,而對引用數據類型只是進行引用的傳遞,而沒有真實的創建一個新的對象,則認為是淺拷貝,比如Spring和Apache的copyProperties和上面自己的拷貝方法。反過來,在對引用數據類型進行拷貝的時候,創建了一個新的對象,並且復制其內的成員變量,則認為是深拷貝。

  通過對比不難發現:深拷貝相當於創建了一個新的對象,只是這個對象的所有內容,都和被拷貝的對象一模一樣,即兩者的修改是隔離的,相互之間沒有影響;而淺拷貝也是創建了一個對象,但是這個對象的某些內容(比如A)依然是被拷貝對象的,即通過這兩個對象中任意一個修改A,兩個對象的A都會受到影響。

  Spring和Apache的copyProperties都屬於淺拷貝,在日常開發中基本可以滿足使用了。需要注意的是,在Java開發手冊中提到,不建議用Apache的BeanUtils工具類,因為其中有很多的檢驗、類型轉換、日志打印等,實現復雜度高,性能比較差。

  那么如果要實現高性能且安全的深度拷貝呢?深拷貝就是被拷貝對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量,那些引用其他對象的變量將指向被拷貝過的新對象,而不再試原有的那些被引用的對象,就是說,深拷貝把要拷貝的對象所引用的對象都復制了一遍。我覺得最靠譜的深拷貝還是要用序列化后寫流的方法,這種方法需要實現Serializable接口,多層拷貝時,引用類型都要實現Serializable接口。代碼如下:

import java.io.*;
import java.util.Date;

public class CopyDemo {
    class User implements Serializable {
        private int id;
        private String username;// 用戶姓名
        private String sex;// 性別
        private Date birthday;// 生日
        private String address;// 地址
        private Person person; //引用類型

        public User myColon(){
            User copy=null;
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(this);
                //將流序列化成對象
                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bais);
                copy = (User) ois.readObject();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return copy;
        }

        //省略get-set方法代碼
    }

    class Person implements Serializable {
        private int id;
        private String userName ;
        private int age ;
        private String mobilePhone ;
        public  Person(){}
        public Person(int id,String userName, int age, String mobilePhone) {
            this.id = id;
            this.userName = userName;
            this.age = age;
            this.mobilePhone = mobilePhone;
        }
        //省略get-set方法
    }
}

  在Java語言里深拷貝一個對象,先使對象實現Serializable接口,然后把對象(實際上只是對象的一個拷貝)寫到一個流里,再從流里讀出來,便可以重建對象。通過代碼可以更明顯的看到這個過程。把對象寫到流里的過程是串行化(Serilization)過程,而把對象從流中讀出來是並行化(Deserialization)過程。需要注意的是,需要拷貝的對象可能包含多層引用類型,多層拷貝不僅要將拷貝對象實現序列化接口,引用對象也同樣的要實現序列化接口。

 

 

 

 

 

 

 

     



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM