Java中把一个对象复制给另外一个对象引发的思考

胜金 2021-02-23 14:33:58
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)过程。需要注意的是,需要拷贝的对象可能包含多层引用类型,多层拷贝不仅要将拷贝对象实现序列化接口,引用对象也同样的要实现序列化接口。

 

 

 

 

 

 

 

 


版权声明
本文为[胜金]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/JohanChan/p/14435823.html

  1. Add filter and execute filter in excel by Java
  2. Dialogue in spring
  3. 解决Docker MySQL无法被宿主机访问的问题
  4. Oracle OCP 19c 认证1Z0-083考试题库(第1题)
  5. Solve the problem that docker MySQL cannot be accessed by the host
  6. Oracle OCP 19C certification 1z0-083 examination question bank (question 1)
  7. 在 2021 年你需要掌握的 7 种关于 JavaScript 的数组方法
  8. Seven array methods for JavaScript you need to master in 2021
  9. 在 2021 年你需要掌握的 7 种关于 JavaScript 的数组方法
  10. Struts2 + Json _ 配置,异常解决及深入了解Struts2返回JSON数据的原理及具体应用范例
  11. Seven array methods for JavaScript you need to master in 2021
  12. Struts2 + Json _ Configuration, exception resolution and in-depth understanding of Struts2 return JSON data principle and specific application examples
  13. (三)MySQL锁机制 + 事务
  14. (3) MySQL lock mechanism + transaction
  15. 在 2021 年你需要掌握的 7 种关于 JavaScript 的数组方法
  16. Seven array methods for JavaScript you need to master in 2021
  17. 基于Kafka和Elasticsearch构建实时站内搜索功能的实践
  18. Practice of building real time search function in the website based on Kafka and elasticsearch
  19. Golang 实现 Redis(9): 使用GeoHash 搜索附近的人
  20. RxHttp - 轻量级、可扩展、易使用、完美兼容MVVM、MVC架构的网络封装类库
  21. Golang 实现 Redis(9): 使用GeoHash 搜索附近的人
  22. RxHttp - 轻量级、可扩展、易使用、完美兼容MVVM、MVC架构的网络封装类库
  23. Golang realizes redis (9): using geohash to search nearby people
  24. Rxhttp - lightweight, extensible, easy to use, perfectly compatible with MVVM, MVC architecture network encapsulation class library
  25. Golang realizes redis (9): using geohash to search nearby people
  26. Rxhttp - lightweight, extensible, easy to use, perfectly compatible with MVVM, MVC architecture network encapsulation class library
  27. 读懂框架设计的灵魂 — Java 反射机制
  28. 治疗磁盘空间不足焦虑症,释放容器占用空间——Win10+docker篇
  29. 别再用jodatime了!全网最权威Java8日期时间类LocalDate、LocalDateTime详解
  30. Understanding the soul of framework design java reflection mechanism
  31. 配置客户端以安全连接到Apache Kafka集群4:TLS客户端身份验证
  32. Treating anxiety of insufficient disk space and releasing space occupied by containers -- win10 + docker
  33. Don't use jodatime any more! The most authoritative java 8 date and time classes in the whole network: detailed explanation of localdate and localdatetime
  34. Configure clients to connect securely to Apache Kafka Cluster 4: TLS client authentication
  35. Spring break
  36. 高性能MySQL(三):Schema与数据类型优化
  37. High performance mysql (3): schema and data type optimization
  38. redis解决缓存、击穿、雪崩
  39. redis
  40. 骑士卡:基于Kafka搭建消息中心,上亿消息推送轻松完成
  41. Redis solves cache, breakdown and avalanche
  42. redis
  43. Knight card: build a message center based on Kafka, and push hundreds of millions of messages easily
  44. Oracle OCP 19c 认证1Z0-083考试题库(第2题)
  45. redis的三种模式
  46. kubernetes和docker----2.学习Pod资源
  47. 谈一谈如何远程访问MySQL(腾讯云,云主机)
  48. Linux(五):Linux的文档编辑器Vi
  49. Oracle OCP 19C certification 1z0-083 examination question bank (question 2)
  50. 云原生项目实践DevOps(GitOps)+K8S+BPF+SRE,从0到1使用Golang开发生产级麻将游戏服务器—第6篇
  51. kubernetes和docker----2.学习Pod资源
  52. JSP基于Java开发Web应用程序特点有哪些?
  53. Three modes of redis
  54. Kubernetes and docker -- 2. Learning pod resources
  55. Linux (5): the document editor VI of Linux
  56. Cloud native project practice Devops (gitops) + k8s + BPF + SRE, using golang to develop production level mahjong game server from 0 to 1
  57. Kubernetes and docker -- 2. Learning pod resources
  58. What are the characteristics of JSP developing web application based on Java?
  59. Lottie error: java.lang.AssertionError : android.util.JsonReader .peek
  60. Rxhttp - lightweight, extensible, easy to use, perfectly compatible with MVVM, MVC architecture network encapsulation class library