Java中clone方法的使用
什么是clone
在实际编程过程中,我们常常要遇到这种情况:有一个对象object1,在某一时刻object1中已经包含了一些有效值,此时可能会需要一个和object1完全相同新对象object2,并且此后对object2任何改动都不会影响到object1中的值,也就是说,object1与object2是两个独立的对象,但object2的初始值是由object1对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需 求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。
Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone(),该方法在Object中的定义如下:
/**
* Class Object is the root of the class hierarchy. Every class has Object as a superclass.
* All objects, including arrays, implement the methods of this class.
*/
public class Object {
/**
* Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
* The general intent is that, for any object {@code x}, the expression: x.clone() != x will be true,
* and that the expression: x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
* While it is typically the case that: x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
}
从上面对clone方法的注解可知clone方法的通用约定:对于任意一个对象x,表达式①x.clone != x将会是true;表达式②x.clone().getClass()==x.getClass()将会是true,但不是绝对的;表达式③x.clone().equals(x)将会是true,但是这也不是绝对的。
从源代码可知,根类Object的clone方法是用protected关键字修饰,这样做是为避免我们创建每一个类都默认具有克隆能力。
如何使用clone方法
要使类具有克隆能力能力时,需要实现Cloneable接口,实现它的目的是作为一个对象的一个mixin(混入)接口,表明这个对象是允许克隆的。它的源码如下:
/**
* A class implements the Cloneable interface to indicate to the {@link java.lang.Object#clone()} method that
* it is legal for that method to make a field-for-field copy of instances of that class.
*/
public interface Cloneable {
}
可以看出Cloneable是一个空接口(标记接口),它决定了Object中受保护的clone方法的实现行为:如果一个类实现了Cloneable接口,Object的clone方法就返回这个对象的逐域拷贝,否则就抛出CloneNotSupportedException异常。如果实现了这个接口,类和它所有的超类都无需调用构造器就可以创建对象。下面通过一个简单的实例来演示clone方法的使用。
编写一个被克隆对象Student类:
package com.kevin.clone;
/**
* 创建一个简单实例演示clone方法
* @author Kevin
*
*/ public class Student implements Cloneable { private String name;
private String gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
} @Override
protected Student clone() throws CloneNotSupportedException {
return (Student)super.clone();
}
@Override
public String toString() {
return " [name=" + name + ", gender=" + gender + "]";
} }
编写测试代码:
package com.kevin.clone;
/**
* 测试clone方法
* @author Kevin
*
*/ public class test_clone { public static void main(String[] args){
Student student1 = new Student();
student1.setName("Kevin");
student1.setGender("Male");
System.out.println("student1"+student1); try{
Student student2 = student1.clone();
System.out.println("Clone student2 from student1...");
System.out.println("student2"+student2);
System.out.println(student1.equals(student2));
System.out.println("Alter student2...");
student2.setName("Alice");
student2.setGender("Female");
System.out.println("student1"+student1);
System.out.println("student2"+student2);
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
} }
输出结果:
student1 [name=Kevin, gender=Male]
Clone student2 from student1...
student2 [name=Kevin, gender=Male]
false
Alter student2...
student1 [name=Kevin, gender=Male]
student2 [name=Alice, gender=Female]
分析:
● 一是希望能实现clone功能的CloneClass类实现了Cloneable接口,这个接口属于java.lang包, java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。
● 二是重载了clone()方法。最 后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调 用了java.lang.Object类的clone()方法。下面再详细的解释一下这几点。
● 最后仔细观察一下Object类的clone()一个native方法,native方法的效率一般来说都是远高于java中的非 native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了 clone功能。对于第二点,也要观察Object类中的clone()还是一个protected属性的方法。这也意味着如果要应用clone()方 法,必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了。然后重载clone()方法。还有一点要考虑的是为 了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。
那么clone类为什么还要实现Cloneable接口呢?需要注意的是,Cloneable接口是不包含任何方法的,其实这个接口仅仅是一个标志,而且 这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方 法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出 CloneNotSupportedException异常。
影子克隆和深度克隆
下面通过一个实例来演示什么是影子克隆。
编写一个Teacher类(其中包含一个Course属性):
package com.kevin.clone;
/**
* 测试影子克隆方法
* @author Kevin
*
*/ public class Teacher implements Cloneable{
private String name;
private Integer age;
private Course course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
} @Override
protected Teacher clone() throws CloneNotSupportedException {
return (Teacher) super.clone();
}
@Override
public String toString() {
return "Teacher [name=" + name + ", age=" + age + ", course=" + course + "]";
} }
package com.kevin.clone; public class Course { private String name;
private Integer id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "Course [name=" + name + ", id=" + id + "]";
} }
编写一个测试类:
package com.kevin.clone; public class Course { private String name;
private Integer id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "Course [name=" + name + ", id=" + id + "]";
} }
输出结果如下:
teacher1 [name=Kevin, age=22, course=Course [name=Math, id=66]]
Clone teacher2 from teacher1...
teacher2 [name=Kevin, age=22, course=Course [name=Math, id=66]]
Alter teacher2...
teacher1 [name=Kevin, age=22, course=Course [name=English, id=88]]
teacher2 [name=Ryan, age=18, course=Course [name=English, id=88]]
通过分析结果可知,当我们修改克隆对象teacher2的时候,teacher1的course属性也被修改了,如果通过查看内存地址的形式我们可以发现,他们两个course其实是同一个对象。由此我们可以推断,调用clone方法产生的效果是:现在内存中开辟一块和原始对象一样的空间,然后拷贝原始对象中的内容。但是对于基本数据类型,这样的操作是没有问题的,但对于非基本类型,它们保存的仅仅是对象的引用,这就是为什么clone后的非基本类型变量和原始对象中相应的变量会指向的是同一个对象。这就是所谓的影子克隆。
为了解决影子克隆所产生的问题,我们就需要使用深度克隆方案。通过对以上实例改进后的方案如下:
package com.kevin.clone;
/**
* 测试影子克隆方法
* @author Kevin
*
*/ public class Teacher implements Cloneable{
private String name;
private Integer age;
private Course course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
} @Override
protected Teacher clone() throws CloneNotSupportedException {
Teacher teacher = (Teacher)super.clone();
teacher.course = course.clone();
return teacher;
}
@Override
public String toString() {
return " [name=" + name + ", age=" + age + ", course=" + course + "]";
} }
package com.kevin.clone; public class Course implements Cloneable{ private String name;
private Integer id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
} protected Course clone() throws CloneNotSupportedException{
return (Course)super.clone();
} @Override
public String toString() {
return "Course [name=" + name + ", id=" + id + "]";
} }
同样使用原来的测试类:
package com.kevin.clone;
/**
* 测试clone方法
* @author Kevin
*
*/ public class test_clone2 { public static void main(String[] args){
Teacher t1 = new Teacher();
t1.setName("Kevin");
t1.setAge(22);
Course c1 = new Course();
c1.setName("Math");
c1.setId(66);
t1.setCourse(c1);
System.out.println("teacher1"+t1); try{
Teacher t2 = t1.clone();
System.out.println("Clone teacher2 from teacher1...");
System.out.println("teacher2"+t2);
System.out.println("Alter teacher2...");
t2.setName("Ryan");
t2.setAge(18);
//修改courese属性
t2.getCourse().setName("English");
t2.getCourse().setId(88);
System.out.println("teacher1"+t1);
System.out.println("teacher2"+t2);
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
} }
输出结果:
teacher1 [name=Kevin, age=22, course=Course [name=Math, id=66]]
Clone teacher2 from teacher1...
teacher2 [name=Kevin, age=22, course=Course [name=Math, id=66]]
Alter teacher2...
teacher1 [name=Kevin, age=22, course=Course [name=Math, id=66]]
teacher2 [name=Ryan, age=18, course=Course [name=English, id=88]]
分析:由以上运行结果可知,进行过深度克隆之后,对clone产生的teacher2对象的course属性进行修改时,并未影响到原对象teacher1的course属性。
任何类都可以实现深度clone吗
答案是否定的,例如,StringBuffer,看一下 JDK API中关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个 final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和 StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么自己重新生成对象: new StringBuffer(oldValue.toString()); 进行赋值。
要知道除了基本数据类型(byte,short,int,long,double等)可自动实现深度克隆以外,其它例如Integer、String、Double等是一特殊情况。
下面通过一个实例来演示上述结论。
package com.kevin.clone; public class Book implements Cloneable{
public String name;
public StringBuffer author; protected Book clone() throws CloneNotSupportedException{
return (Book)super.clone();
} }
测试代码:
package com.kevin.clone;
/**
* 测试clone方法
* @author Kevin
*
*/ public class test_clone3 { public static void main(String[] args){
Book book = new Book();
book.name = new String("Think in Java");
book.author = new StringBuffer("Kevin");
System.out.println("Before clone book.name :"+book.name);
System.out.println("Before clone book.author :"+book.author);
Book book_clone = null;
try{
book_clone = (Book)book.clone();
}catch(CloneNotSupportedException e){ e.printStackTrace();
}
book_clone.name = book_clone.name.substring(0,5);
book_clone.author = book_clone.author.append(" Zhang");
System.out.println("\nAfter clone book.name :"+book.name);
System.out.println("After clone book.author :"+book.author);
System.out.println("\nAfter clone book_clone.name :"+book_clone.name);
System.out.println("After clone book_clone.author :"+book_clone.author);
} }
输出结果:
Before clone book.name :Think in Java
Before clone book.author :Kevin After clone book.name :Think in Java
After clone book.author :Kevin Zhang After clone book_clone.name :Think
After clone book_clone.author :Kevin Zhang
分析:有上述结果可知,String类型的变量看起来好像实现了深度clone,因为对book_clone.name的改动并没有影响到book.name。实质上,在clone的时候book_clone.name与book.name仍然是引用,而且都指向了同一个 String对象。但在执行book_clone.name = book_clone.name.substring(0,5)的时候,生成了一个新的String类型,然后又赋回给book_clone.name。这是因为String被 Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。类似的,String类中的其它方法也是如此,都是生成一个新的对象返回。当然StringBuffer还是原来的对象。
需要知道的是在Java中所有的基本数据类型都有一个相对应的类,例如Integer类对应int类型,Double类对应double类型等等,这些类也 与String类相同,都是不可以改变的类。也就是说,这些的类中的所有方法都是不能改变其自身的值的。这也让我们在编clone类的时候有了一个更多的 选择。同时我们也可以把自己的类编成不可更改的类。
Java中clone方法的使用的更多相关文章
- 分析java中clone()方法 (转载+修改)
Java中的clone() 方法 java所有的类都是从java.lang.Object类继承而来的,而Object类提供下面的方法对对象进行复制. protected native Object c ...
- Java基础——clone()方法浅析
一.clone的概念 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...
- java中clone的深入理解
Java中Clone的概念大家应该都很熟悉了,它可以让我们很方便的“制造”出一个对象的副本来,下面来具体看看java中的Clone机制是如何工作的? 1. Clone和Copy 假 ...
- Java中clone的写法
Cloneable这个接口设计得十分奇葩,不符合正常人的使用习惯,然而用这个接口的人很多也很有必要,所以还是有必要了解一下这套扭曲的机制.以下内容来自于对Effective Java ed 2. it ...
- Java的clone方法效率问题
在Java中,经常会需要新建一个对象,很多情况下,需要这个新建的对象和现有的某个对象保持属性一致. 那么,就有两种方式来实现这个对象的构造: ①通过新建一个对象,为这个对象的属性根据原有对象的属性来进 ...
- Java中的方法应用
一.如何定义java中的方法 所谓方法,就是用来解决一类问题的代码的有序组合,是一个功能模块. 语法: 1. 访问修饰符:方法允许被访问的权限范围, 可以是 public.protected.priv ...
- c#和java中的方法覆盖——virtual、override、new
多态和覆盖 多态是面向对象编程中最为重要的概念之一,而覆盖又是体现多态最重要的方面.对于像c#和java这样的面向对象编程的语言来说,实现了在编译时只检查接口是否具备,而不需关心最终的实现,即最终的实 ...
- Java中的方法(形参及实参)return返回类型
如何定义 Java 中的方法 所谓方法,就是用来解决一类问题的代码的有序组合,是一个功能模块. 一般情况下,定义一个方法的语法是: 其中: 1. 访问修饰符:方法允许被访问的权限范围, 可以是 pub ...
- java中的方法method
java中的方法必须存在于类class里,不能独立存在.类是描述具有某种特征的事物,方法则是这类 事物具有的某种功能,通过调用方法可以实现某种特定的功能.方法名一般以小写的动词开头. 例: publi ...
随机推荐
- 使用swagger管理接口
swagger 配置 1.pom 增加jar包依赖 <dependency> <groupId>io.springfox</groupId> <artifac ...
- Tracert(跟踪路由)是路由跟踪实用程序,用于确定 IP 数据包访问目标所采取的路径。
Tracert(跟踪路由)是路由跟踪实用程序,用于确定 IP 数据包访问目标所采取的路径. Tracert 命令用 IP 生存时间 (TTL) 字段和 ICMP 错误消息来确定从一个主机到网络上其 ...
- MySQL数据库开发规范知识点
前言: 设计规范更多的是为了确保数据库设计的合理性.为了项目最终的协调稳定性,而命名规范则更多的是为了确保设计的正式和统一. 约定优先于配置(Convention Over Configuration ...
- IEEE发布2017年编程语言排行榜:Python高居首位
https://news.cnblogs.com/n/574248 编者按:本文由微信公众号“机器之心”(ID:almosthuman2014)编译,机器之心专注生产 AI 领域专业性内容.本文作者: ...
- 【js-xlsx和file-saver插件】前端html的table导出数据到excel的表格合并显示boder
最近在做项目,需要从页面的表格中导出excel,一般导出excel有两种方法:一.习惯上是建模版从后台服务程序中导出:二.根据页面table中导出:综合考虑其中利弊选择二.根据页面table中导出ex ...
- linux常用命令合集(未完)
(1)新建 新建文件夹:mkdir 文件夹名 新建文件:touch 文件路径/文件名 删除: rm –f filename删除文件 rm –rf filename删除文件夹 (2)重命名 1将一个名为 ...
- mac上php版本切换
目标:Mac 环境下完成 php 版本之间的切换 在本地开发中很多时候我们需要多个版本的 php 开发环境.在公司中习惯用自己电脑开发的伙伴们,常常因为公司线上环境被迫更换php版本.但有不想降低自己 ...
- SSM博客 前端页面样式不显示
<!-- 由于在web.xml中定义的url拦截形式为“/”表示拦截所有的url请求, 包括静态资源例如css.js等.所以需要在springmvc.xml中添加资源映射标 --> < ...
- 前端leader找我谈心:我是如何从刚毕业的前端菜鸟一步步成长为前端架构师的?
谈谈学习 我做前端已经有五年的时间了,从大学刚毕业的时候,我是一个完全什么都不懂的小白.虽然我大学里学的是软件工程专业,但是因为在大学里荒废学业,每天只知道打游戏,基本上到大学毕业之前我是什么都不会的 ...
- JAVAEE——Mybatis第一天:入门、jdbc存在的问题、架构介绍、入门程序、Dao的开发方法、接口的动态代理方式、SqlMapConfig.xml文件说明
1. 学习计划 第一天: 1.Mybatis的介绍 2.Mybatis的入门 a) 使用jdbc操作数据库存在的问题 b) Mybatis的架构 c) Mybatis的入门程序 3.Dao的开发方法 ...