实现java对象Copy的三种方式

一、克隆

implements Cloneable

二、序列化

implements Serializable

三、利用反射机制copy

apache的BeanUtils方案

使用org.apache.commons.beanutils.BeanUtils进行对象深入复制时候,主要通过向BeanUtils框架注入新的类型转换器,因为默认情况下,BeanUtils对复杂对象的复制是引用,

可以发现,使用org.apache.commons.beanutils.BeanUtils复制引用时,主和源的引用为同一个,即改变了主的引用属性会影响到源的引用,所以这是一种浅拷贝

apache的PropertyUtils方案

PropertyUtils的copyProperties()方法几乎与BeanUtils.copyProperties()相同,主要的区别在于后者提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,PropertyUtils不支持这个功能,所以说BeanUtils使用更普遍一点,犯错的风险更低一点。而且它仍然属于浅拷贝。

Apache提供了 SerializationUtils.clone(T),T对象需要实现 Serializable 接口,他属于深克隆。

spring的BeanUtils方案

Spring中的BeanUtils,其中实现的方式很简单,就是对两个对象中相同名字的属性进行简单get/set,仅检查属性的可访问性。

可以看到, 成员变量赋值是基于目标对象的成员列表, 并且会跳过ignore的以及在源对象中不存在的, 所以这个方法是安全的, 不会因为两个对象之间的结构差异导致错误, 但是必须保证同名的两个成员变量类型相同.

dozer

Dozer(http://dozer.sourceforge.net/)能够实现深拷贝。Dozer是基于反射来实现对象拷贝,反射调用set/get 或者是直接对成员变量赋值 。 该方式通过invoke执行赋值,实现时一般会采用beanutil, Javassist等开源库。

MapStrcut

MapStrcut属于编译期的对象复制方案,它能够动态生成set/get代码的class文件 ,在运行时直接调用该class文件。该方式实际上扔会存在set/get代码,只是不需要自己写了。

BeanCopier

可以通过缓存BeanCopier的实例来提高性能。

fastjson和GSON

使用fastjson和GSON主要是通过对象json序列化和反序列化来完成对象复制,这里只是提供一种不一样的对象拷贝的思路,例子略。

Spring的BeanUtils比较稳定,不会因为量大了,耗时明显增加,但其实基准耗时比较长;apache的BeanUtils稳定性与效率都不行,不可取;Gson,因为做两个gson转换,所以正常项目中,可能耗时会更少一些;PojoUtils稳定不如spring,但是总耗时优势明显,原因是它只是根据项目的需求,实现的简单的转换模板,这个代码在其它的几个工具类均有。

而在网上的其他Blog中(参见Reference),对Apache的BeanUtils、PropertyUtils和CGLIB的BeanCopier作了性能测试。

测试结果:

性能对比: BeanCopier > BeanUtils. 其中BeanCopier的性能高出另外两个100数量级。

综上推荐使用:

  1. BeanUtils(简单,易用)
  2. BeanCopier(加入缓存后和手工set的性能接近)
  3. Dozer(深拷贝)
  4. fastjson(特定场景下使用)

一、克隆

Java中创建对象除了调用构造函数来创建以外,还有几种利用语言之外的机制来创建的方式,clone就是其中的一种(还有一种就是前面说过的序列化的方式)。

比如我们有一个Person类,有两个属性,一个年龄age,一个姓名name(又是熟悉的Person~)

(1)浅拷贝

实现浅拷贝的步骤是:

  1. 被复制的类需要实现Cloneable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常)改接口为标记接口(不含任何方法)
  2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象,(native为本地方法)

javaBean:

public class Person implements Cloneable{
private int age;
private String name; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}

测试类:

public class CloneTest {

    public static void main(String[] args) throws CloneNotSupportedException {
Person person1=new Person();
person1.setAge(10);
person1.setName("sxf"); Person person2=person1.clone();
person2.setName("chn");
//运行结果:
//person1 name:sxf
//person2 name:chn
System.out.println("person1 name:"+person1.getName());
System.out.println("person2 name:"+person2.getName()); }
}

如果类中含有引用类型属性,则浅copy失效。

其实原理很简单,clone的对象根据原始对象从堆中开辟一块同等大小的内存,然后把原始对象的数据都复制到新的内存地址,对于基本类型,可以把原始值复制过来,但是对于引用类型,其保存的只是一个地址,复制时也是对地址的复制,最终还是指向同一个对象,所以也就造成了下面的问题。

给javaBean新增一个引用属性的Bean的信息

public class Person implements Cloneable{
private int age;
private String name;
private Pet pet; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Pet getPet() {
return pet;
} public void setPet(Pet pet) {
this.pet = pet;
} @Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
} public class Pet {
private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}

测试类:

public class CloneTest {

    public static void main(String[] args) throws CloneNotSupportedException {
Person person1=new Person();
person1.setAge(10);
person1.setName("sxf");
Pet pet1=new Pet();
pet1.setName("xx");
person1.setPet(pet1); Person person2=person1.clone();
person2.setName("chn");
person2.getPet().setName("yy");
//运行结果:
//person1 name:sxf
//person2 name:chn
//person1 pet name:yy
//person2 pet name:yy
System.out.println("person1 name:"+person1.getName());
System.out.println("person2 name:"+person2.getName());
System.out.println("person1 pet name:"+person1.getPet().getName());
System.out.println("person2 pet name:"+person2.getPet().getName()); }
}

(2)深拷贝

深拷贝就是把一个对象中的所有对象都拷贝一遍。

就上述例子进行改良的bean

public class Person implements Cloneable {
private int age;
private String name;
private Pet pet; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Pet getPet() {
return pet;
} public void setPet(Pet pet) {
this.pet = pet;
} @Override
protected Person clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
Pet pet = this.pet.clone();
p.setPet(pet);
return p;
}
} public class Pet implements Cloneable{
private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
protected Pet clone() throws CloneNotSupportedException {
return (Pet) super.clone();
}
}

测试类

public class CloneTest {

    public static void main(String[] args) throws CloneNotSupportedException {
Person person1=new Person();
person1.setAge(10);
person1.setName("sxf");
Pet pet1=new Pet();
pet1.setName("xx");
person1.setPet(pet1); Person person2=person1.clone();
person2.setName("chn");
person2.getPet().setName("yy");
//运行结果:
//person1 name:sxf
//person2 name:chn
//person1 pet name:xx
//person2 pet name:yy
System.out.println("person1 name:"+person1.getName());
System.out.println("person2 name:"+person2.getName());
System.out.println("person1 pet name:"+person1.getPet().getName());
System.out.println("person2 pet name:"+person2.getPet().getName()); }
}

但是这样写还会存在一个问题,如果一个对象中嵌套的对象比较多,我们还得去把每一个对象都实现Cloneable接口,如果对象里面嵌套对象,clone方法的处理也会比较复杂,有没有一种简单的方式去实现呢?

还记得我们之前学习过的序列化方式吗?通过输入输出流来实现对象的深拷贝,这也是我们最开始提到的创建对象的另一种方式。

二、序列化

javaBean的改造

package com.spring.test.service.javacore.clone;

import java.io.Serializable;

public class Person implements Serializable {
private int age;
private String name;
private Pet pet; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Pet getPet() {
return pet;
} public void setPet(Pet pet) {
this.pet = pet;
} } public class Pet implements Serializable{ private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}

测试类

public class CloneTest {

    public static void main(String[] args) {
Person person1=new Person();
person1.setName("sxf");
person1.setAge(10);
Pet pet1=new Pet();
pet1.setName("xx");
person1.setPet(pet1);
Person person2=SerializationUtils.clone(person1);
person2.setName("chn");
person2.getPet().setName("yy");
//person1 name:sxf
//person2 name:chn
//pseron1 pet1 name:xx
//pseron2 pet2 name:yy
System.out.println("person1 name:"+person1.getName());
System.out.println("person2 name:"+person2.getName());
System.out.println("pseron1 pet1 name:"+person1.getPet().getName());
System.out.println("pseron2 pet2 name:"+person2.getPet().getName()); }
}

【java编程】java对象copy的更多相关文章

  1. java编程思想---对象

    一.对象 对于每种语言来说,都有自己操纵内存中元素的方法. 在java中,一切被视为对象.可是操纵对象的是一个"引用".举个样例,能够比作为遥控器对电视的操作,遥控器就是引用,而电 ...

  2. JAVA基础语法:java编程规范和常用数据类型(转载)

    JAVA基础语法:java编程规范和常用数据类型 摘要 本文主要介绍了最基本的java程序规则,和常用数据类型,其中侧重说了数组的一些操作. 面向java编程 java是纯面向对象语言,所有的程序都要 ...

  3. 开始JAVA编程的敲门砖——JAVA开发环境搭建

    从头开始的java编程--JAVA开发环境搭建 一.什么是java的开发环境? 顾名思义java的开发环境是提供并保证整个java程序开发运行的必要的环境,搭建java开发环境是开始java编程的敲门 ...

  4. [Java编程思想-学习笔记]第1章 对象导论

    1.1  抽象过程 Java是一门面向对象的语言,它的一个优点在于只针对待解问题抽象,而不用为具体的计算机结构而烦心,这使得Java有完美的移植性,也即Java的口号"Write Once, ...

  5. Java编程思想读书笔记(一)【对象导论】

    2018年1月7日15:45:58 前言 作为学习Java语言的经典之作<Java编程思想>,常常被人提起.虽然这本书出版十年有余,但是内容还是很给力的.很多人说这本书不是很适合初学者,我 ...

  6. Java编程思想读书笔记(二)【一切都是对象】

    begin 2018年1月9日17:06:47 第二章 一切都是对象 Java语言假设我们只进行面向对象的程序设计. 2.1 用引用操纵对象 每种编程语言都有自己的操纵内存元素的方式 操纵内存元素的方 ...

  7. Java编程思想 4th 第2章 一切都是对象

    Java是基于C++的,但Java是一种更纯粹的面向对象程序设计语言,和C++不同的是,Java只支持面向对象编程,因此Java的编程风格也是纯OOP风格的,即一切都是类,所有事情通过类对象协作来完成 ...

  8. Java编程思想 4th 第1章 对象导论

    所有编程语言都提供抽象机制. 面向对象编程似乎是一种很好的编程思想和方式,面向对象编程中的对象简洁描述是:对象具有状态.行为和标识.状态指的是数据存储,存储的数据能反应状态:行为指的是方法,方法表示对 ...

  9. JAVA编程思想读书笔记(四)--对象的克隆

    接上篇JAVA编程思想读书笔记(三)--RTTI No1: 类的克隆 public class MyObject implements Cloneable { int i; public MyObje ...

随机推荐

  1. C#对config配置文件的管理

    应用程序配置文件,对于asp.net是 web.config,对于WINFORM程序是App.Config(ExeName.exe.config). 配置文件,对于程序本身来说,就是基础和依据,其本质 ...

  2. PHP自带调试函数

    1.var_dump:打印变量的相关信息 $a = array(1, 2, array("a", "b", "c")); var_dump( ...

  3. Spring boot实现监听Redis key失效事件实现和其它方式

    需求: 处理订单过期自动取消,比如下单30分钟未支付自动更改订单状态 用户绑定隐私号码当订单结束取消绑定等 解决方案1: 可以利用redis自带的key自动过期机制,下单时将订单id写入redis,过 ...

  4. English trip -- VC(情景课)4 C My feet hurt 我脚痛

    xu言: You're the best... Grammar focus 语法点: eye  eyes hand hands foot feet tooth  teeth arm arms leg  ...

  5. FMUtils.KeyboardHook 轻量级键盘监听器

    Nuget添加引用 Install-Package FMUtils.KeyboardHook 调用示例: var KeyboardHook = new Hook("Global Action ...

  6. div节点的操作(添加,删除,替换,克隆)

    <html> <head> <title></title> <style type="text/css"> div{ b ...

  7. C# 中的时间(DataTime)

    在做报表或查询的时候,常常会预设一些可选的日期范围,如本周.本月.本年等,利用 C# 内置的DateTime基本上都可以实现这些功能. 当前时间: DateTime dt = DateTime.Now ...

  8. windows下面使用nginx配置web注意问题

    1.路径一定要用两个反斜杠进行转义,如果只用单个反斜杠,遇到\n就识别不到路径了,例如下图中的\news中包含\n

  9. SSH 绑定本地端口

    SSH可以传送数据,那么我们可以让那些不加密的网络连接,全部改走SSH连接,从而提高安全性. 假定我们要让8080端口的数据,都通过SSH传向远程主机,命令就这样写: $ user@host SSH会 ...

  10. 【CSS】清除浮动的五种方式

    清除浮动是一件功德无量的事情23333 这里记录一下清除浮动的多种方式 *首先要明确的是,为什么要清除浮动? A 影响其他元素定位 父盒子高度为0,子盒子全部浮动.定位,子盒子不会撑开父盒子,下面的元 ...