热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Java对象的复制三种方式(小结)

这篇文章主要介绍了Java对象的复制三种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1、概述

在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。例如下面程序展示的情况:

class Student { 
  private int number; 
 
  public int getNumber() { 
    return number; 
  } 
 
  public void setNumber(int number) { 
    this.number = number; 
  } 
   
} 
public class Test {  
  public static void main(String args[]) { 
    Student stu1 = new Student(); 
    stu1.setNumber(12345); 
    Student stu2 = stu1; 
    stu1.setNumber(54321); 
    System.out.println("学生1:" + stu1.getNumber()); 
    System.out.println("学生2:" + stu2.getNumber()); 
  } 
}

结果:

学生1:54321
学生2:54321

为什么改变学生2的学号,学生1的学号也发生了变化呢?

原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,

这样,stu1和stu2指向内存堆中同一个对象。如图:

那么,怎么能干干净净清清楚楚地复制一个对象呢。在 Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求有很多途径,

(1)将A对象的值分别通过set方法加入B对象中;
(2)通过重写java.lang.Object类中的方法clone();
(3)通过org.apache.commons中的工具类BeanUtils和PropertyUtils进行对象复制;
(4)通过序列化实现对象的复制。

2、将A对象的值分别通过set方法加入B对象中

对属性逐个赋值,本实例为了演示简单就设置了一个属性:

Student stu1 = new Student(); 
stu1.setNumber(12345); 
Student stu2 = new Student(); 
stu2.setNumber(stu1.getNumber());

我们发现,属性少对属性逐个赋值还挺方便,但是属性多时,就需要一直get、set了,非常麻烦。

3、重写java.lang.Object类中的方法clone()

先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。

在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。

3.1 浅克隆

一般步骤:

被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)

class Student implements Cloneable{ 
  private int number; 
 
  public int getNumber() { 
    return number; 
  } 
 
  public void setNumber(int number) { 
    this.number = number; 
  } 
   
  @Override 
  public Object clone() { 
    Student stu = null; 
    try{ 
      stu = (Student)super.clone(); 
    }catch(CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    return stu; 
  } 
} 
public class Test { 
  public static void main(String args[]) { 
    Student stu1 = new Student(); 
    stu1.setNumber(12345); 
    Student stu2 = (Student)stu1.clone(); 
     
    System.out.println("学生1:" + stu1.getNumber()); 
    System.out.println("学生2:" + stu2.getNumber()); 
     
    stu2.setNumber(54321); 
   
    System.out.println("学生1:" + stu1.getNumber()); 
    System.out.println("学生2:" + stu2.getNumber()); 
  } 
} 

结果:
学生1:12345
学生2:12345
学生1:12345
学生2:54321

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。


在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。

3.2 深克隆

package abc; 
 
class Address { 
  private String add; 
 
  public String getAdd() { 
    return add; 
  } 
 
  public void setAdd(String add) { 
    this.add = add; 
  }  
} 
 
class Student implements Cloneable{ 
  private int number; 
 
  private Address addr; 
   
  public Address getAddr() { 
    return addr; 
  } 
 
  public void setAddr(Address addr) { 
    this.addr = addr; 
  } 
 
  public int getNumber() { 
    return number; 
  } 
 
  public void setNumber(int number) { 
    this.number = number; 
  } 
   
  @Override 
  public Object clone() { 
    Student stu = null; 
    try{ 
      stu = (Student)super.clone();  //浅复制 
    }catch(CloneNotSupportedException e) { 
      e.printStackTrace(); 
    }  
    return stu; 
  } 
} 
public class Test { 
   
  public static void main(String args[]) { 
     
    Address addr = new Address(); 
    addr.setAdd("杭州市"); 
    Student stu1 = new Student(); 
    stu1.setNumber(123); 
    stu1.setAddr(addr); 
     
    Student stu2 = (Student)stu1.clone(); 
     
    System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); 
    System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 
     
    addr.setAdd("西湖区"); 
     
    System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); 
    System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 
  } 
}

结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:西湖区

怎么两个学生的地址都改变了?

原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:

package abc; 
 
class Address implements Cloneable { 
  private String add; 
 
  public String getAdd() { 
    return add; 
  } 
 
  public void setAdd(String add) { 
    this.add = add; 
  } 
   
  @Override 
  public Object clone() { 
    Address addr = null; 
    try{ 
      addr = (Address)super.clone(); 
    }catch(CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    return addr; 
  } 
} 
 
class Student implements Cloneable{ 
  private int number; 
 
  private Address addr; 
   
  public Address getAddr() { 
    return addr; 
  } 
 
  public void setAddr(Address addr) { 
    this.addr = addr; 
  } 
 
  public int getNumber() { 
    return number; 
  } 
 
  public void setNumber(int number) { 
    this.number = number; 
  } 
   
  @Override 
  public Object clone() { 
    Student stu = null; 
    try{ 
      stu = (Student)super.clone();  //浅复制 
    }catch(CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    stu.addr = (Address)addr.clone();  //深度复制 
    return stu; 
  } 
} 
public class Test { 
   
  public static void main(String args[]) { 
     
    Address addr = new Address(); 
    addr.setAdd("杭州市"); 
    Student stu1 = new Student(); 
    stu1.setNumber(123); 
    stu1.setAddr(addr); 
     
    Student stu2 = (Student)stu1.clone(); 
     
    System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); 
    System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 
     
    addr.setAdd("西湖区"); 
     
    System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); 
    System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 
  } 
}

结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:杭州市

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。


在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。

(如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。)

4、工具类BeanUtils和PropertyUtils进行对象复制

Student stu1 = new Student(); 
stu1.setNumber(12345); 
Student stu2 = new Student(); 
BeanUtils.copyProperties(stu2,stu1);

这种写法无论多少种属性都只需要一行代码搞定,很方便吧!除BeanUtils外还有一个名为PropertyUtils的工具类,它也提供copyProperties()方法,作用与BeanUtils的同名方法十分相似,主要的区别在于BeanUtils提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而PropertyUtils不支持这个功能,但是速度会更快一些。在实际开发中,BeanUtils使用更普遍一点,犯错的风险更低一点。

5、通过序列化实现对象的复制

序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可

以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。


推荐阅读
  • 在本周的白板演练中,Apache Flink 的 PMC 成员及数据工匠首席技术官 Stephan Ewen 深入探讨了如何利用保存点功能进行流处理中的数据重新处理、错误修复、系统升级和 A/B 测试。本文将详细解释保存点的工作原理及其应用场景。 ... [详细]
  • 本文详细介绍了 Java 中 org.apache.qpid.server.model.VirtualHost 类的 closeAsync() 方法,提供了具体的代码示例和应用场景。通过这些示例,读者可以更好地理解和使用该方法。 ... [详细]
  • 本文详细介绍了 Java 中的 org.apache.hadoop.registry.client.impl.zk.ZKPathDumper 类,提供了丰富的代码示例和使用指南。通过这些示例,读者可以更好地理解如何在实际项目中利用 ZKPathDumper 类进行注册表树的转储操作。 ... [详细]
  • This pull request introduces the ability to provide comprehensive paragraph configurations directly within the Create Note and Create Paragraph REST endpoints, reducing the need for additional configuration calls. ... [详细]
  • 在成功安装和测试MySQL及Apache之后,接下来的步骤是安装PHP。为了确保安全性和配置的一致性,建议在安装PHP前先停止MySQL和Apache服务,并将MySQL集成到PHP中。 ... [详细]
  • 在Java编程中,将字符串转换为整数类型时,必须确保该字符串表示的数值在int类型的取值范围内。如果超出范围,将会抛出异常。本文介绍如何安全地进行这种转换,并提供详细的代码示例。 ... [详细]
  • 阿里云ecs怎么配置php环境,阿里云ecs配置选择 ... [详细]
  • Netflix利用Druid实现高效实时数据分析
    本文探讨了全球领先的在线娱乐公司Netflix如何通过采用Apache Druid,实现了高效的数据采集、处理和实时分析,从而显著提升了用户体验和业务决策的准确性。文章详细介绍了Netflix在系统架构、数据摄取、管理和查询方面的实践,并展示了Druid在大规模数据处理中的卓越性能。 ... [详细]
  • 本文介绍了如何利用 Spring Boot 和 Groovy 构建一个灵活且可扩展的动态计算引擎,以满足钱包应用中类似余额宝功能的推广需求。我们将探讨不同的设计方案,并最终选择最适合的技术栈来实现这一目标。 ... [详细]
  • 本文详细介绍了如何在 MySQL 中授予和撤销用户权限。包括创建用户、赋予不同级别的权限(如表级、数据库级、服务器级)、使权限生效、查看用户权限以及撤销权限的方法。此外,还提供了常见错误及其解决方法。 ... [详细]
  • Hadoop发行版本选择指南:技术解析与应用实践
    本文详细介绍了Hadoop的不同发行版本及其特点,帮助读者根据实际需求选择最合适的Hadoop版本。内容涵盖Apache Hadoop、Cloudera CDH等主流版本的特性及应用场景。 ... [详细]
  • 本文详细探讨了 org.apache.hadoop.ha.HAServiceTarget 类中的 checkFencingConfigured 方法,包括其功能、应用场景及代码示例。通过实际代码片段,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 小编给大家分享一下如何移除URL中的index.php,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收 ... [详细]
  • 本文详细介绍了在XAMPP环境中如何修改Apache和MySQL的默认端口号,并确保WordPress能够正常访问。同时,提供了针对Go语言社区和Golang开发者的相关建议。 ... [详细]
  • Java项目分层架构设计与实践
    本文探讨了Java项目中应用分层的最佳实践,不仅介绍了常见的三层架构(Controller、Service、DAO),还深入分析了各层的职责划分及优化建议。通过合理的分层设计,可以提高代码的可维护性、扩展性和团队协作效率。 ... [详细]
author-avatar
于昕会_445
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有