Java Object 对象拷贝答疑

@author ixenos

摘要:在对象的clone过程需要注意的几点、关于关键字this、super

关于clone[对象拷贝]


  在实际编程过程,有时候我们会遇到一种情况:当你有一个对象A,在某一个时刻,A已经保存了对应的属性值,而且这些值本身是有效的,这个时候可能需要一个和A完全相同的对象B,并且当B里面的属性值发生变化的时候,A中的属性值不受影响,可以理解为A和B独立,但是B的初始化不是按照我们平时创建该对象的时候的初始化操作,B的初始化数据完全来自A。

  对Java存储模型了解的人都明白,在Java里面如果针对两个对象引用采取赋值操作的时候,仅仅是让两个引用指向了同一对象(即值传递,并非引用传递),如果其中一个引用里面的对象属性改变的时候会影响另外一个对象属性跟着改变,所以Java语言本身的对象赋值语句是不能完成上边的需求的。在这种时候,就需要用到Object类里面的通用方法clone(),这里需要说明的是:

  通过clone()方法创建的对象是一个新对象,它可以认为是源对象的一个拷贝,但是在内存堆中,JVM会为这个拷贝分配新的对象存储空间来存放该对象的所有状态。该拷贝和普通对象使用new操作符创建的对象唯一的区别在于初始值,这个拷贝的初始值不是对象里面成员的默认值,而是和源对象此刻状态的成员的值是一样的

  下边这段代码是clone方法的运用:

public class Testing {
public static void main(String args[]){
AClass class1 = new AClass();
class1.a = 12;
AClass class2 = (AClass)class1.clone();
System.out.println(class2.a);
System.out.println(class1==class2); }
} class AClass implements Cloneable{
public int a = 0;
public Object clone(){
AClass o = null;
try{
o = (AClass)super.clone();
}catch(CloneNotSupportedException ex){
ex.printStackTrace();
}
return o;
}
}
  上边这段代码运行结果输出为:
12
false

  可以知道的就是成功复制了一个AClass的对象,该对象的引用为class1,而拷贝对象的引用为class2,这两个引用通过==比较输出为false,证明这两个引用不是指向了同一个对象,而且拷贝对象里面的a的值和class1引用的对象里面的a的值是一样的,都是12,这样就成功完成了对象的拷贝过程。

在对象的clone过程,需要注意的几点


  1.希望能够提供对象clone功能的类必须实现Cloneable接口,这个接口位于java.lang包里面
  2.希望提供对象clone功能的类必须重载clone()方法,在重载过程可以看到这句话:super.clone();也就是说,不论clone类的继承结构如何,我们在对象拷贝的时候都直接或间接调用了Object的clone()方法。而且细心留意可以看到Object的clone方法是protected域的,也就是说这个方法只有Object的子类可以调用,而在重载的时候将clone方法修饰符改为public
  3.还有一点很重要就是Object源代码里面的clone()方法是native方法,一般而言,对JVM来说,native方法的效率远比java中的普通方法,这就是为什么我们在复制一个对象的时候使用Object的clone()方法,而不是使用new的方式
  4.Cloneable接口和我们在编写IO程序的时候序列化接口一样,只是一个标志,这个接口是不包含任何方法的,这个标志主要是为了检测Object类中的clone方法,若我们定义的类想要实现拷贝功能,但是没有实现该接口而调用Object的clone方法,那么就会出现语句中catch块里面的异常错误,抛出CloneNotSupportedException。

关于浅拷贝


  在对象clone的过程中,浅拷贝又称为“影子clone”,先看一段代码:

//这里先定义一个类
class AClass{
public int a;
public AClass(int a){ this.a = a;}
public void change(){ a += 12;}
public String toString(){ return "A Value is " + this.a;}
}
//定义一个clone类,里面包含了AClass的对象引用
class BClass implements Cloneable{
public int a = 12;
public AClass obj = new AClass(11);
public Object clone(){
BClass object = null;
try{
object = (BClass)super.clone();
}catch(CloneNotSupportedException ex){
ex.printStackTrace();
}
return object;
}
} public class TestClone {
public static void main(String args[]){
BClass class1 = new BClass();
class1.a = 15; System.out.println(class1.a);
System.out.println(class1.obj);
BClass class2 = (BClass)class1.clone();
class2.a = 22;
class2.obj.change();
System.out.println(class1.a);
System.out.println(class1.obj);
System.out.println(class2.a);
System.out.println(class2.obj);
}
}
  运行上边这段代码会有以下输出:
15
A Value is 11
15 //这里拷贝成功了
A Value is 23 //!!!不对,根本没有调用class1里面的obj的change方法,所以不应该修改class1里面的obj里面的变量a的值【初衷】
22
A Value is 23

  虽然class2引用的对象是从class1拷贝过来的,class2里面的引用obj和class1里面的引用obj实际上还是指向了同一个对象,其含义在于,拷贝的初衷是要复制一个一模一样的对象,包括对象里面的对象也应该实现的是复制操作,它最终的目的是保证class1和class2本身的属性以及class1和class2里面的对象引用的属性在拷贝过后的各种相关操作里面相互独立,上边输出证明了class1和class2里面的变量a确实已经拷贝成功,但是class1和class2里面的AClass对象的引用obj在拷贝过后还是指向了同一个对象,所以拷贝结束过后,调用class2的obj的change方法的时候,也修改了class1里面的obj指向的对象里面的值。所以在Java里面我们把上边的拷贝过程称为“浅拷贝”,同样又称为“影子clone”。
  从这里可以知道,在JVM的对象复制里面,实际上基本数据类型可以直接通过这种方式来进行拷贝工作,而非原始类型这样操作了过后拷贝的对象仅仅拷贝了对象里面的基本数据类型的成员变量,而比较复杂的类型的成员变量并没有像预期一样产生拷贝效果,这种拷贝我们就称之为“浅拷贝”。

关于深拷贝


  如果要实现我们预期的对象拷贝效果,就需要使用深拷贝操作,其实在浅拷贝基础上实现深拷贝有两个步骤,以上边的代码为例:
  第一步:让AClass实现同样的clone功能
  第二步:在BClass的clone操作中多写入一句话:object.obj =
(AClass)obj.clone();

关于关键字this、super


  在Java语言的类定义中,有两个内置对象,一个是this,一个是super,这两个对象的含义如下:
  this是一个指向对象自己的引用,该引用指向的对象为该类实例化的对象本身;
  super是一个指向该类父类的引用,如果该类不继承于任何类,那么该类调用super引用的就是Object类
  这里用一小段代码说明:

class AClass{
public AClass(int a){
System.out.println("A Init "+ a);
} public void fun(int a){
System.out.println("fun int "+a);
} }
class BClass extends AClass{
private int a = 24; private static int b = 11; public BClass(){
this(12); //this.b = 12; super.fun(12); }
public BClass(int a) {
super(12); System.out.println("B init " + this.a); } }

  针对以上代码段做几个详细的说明:
  super.fun(12):在子类中访问父类的成员的时候使用super关键字,但是super不能访问父类的private成员;
  super(12):子类如果要调用父类的构造函数,可以直接使用super(param)这中调用方式,但是如果是调用父类的构造方法,是不能够在子类的其他函数块里面进行的,只能在构造函数中进行,而且一般不能和this(param)同时出现,关于它们在构造函数中的使用,将在小节7中介绍;
  //this.b=12:虽然这句话注释掉了,但是这句话是可以通过JVM编译的,这里需要区分的是在static块中调用非static成员和在普通块中调用static成员,在普通语句中调用static成员是可以使用this.VAR的方式进行操作的,但是一般不提倡这样做,因为一般调用static成员在编程的时候都是使用独一无二的方式Class.VAR,而不使用this.VAR。
  this(12)/this.a:this在调用自己的成员的构造函数的时候,直接使用this(param)方式调用构造函数,如果是调用普通的成员就使用后者。
  *:this和super都不能放在static块中运行,不论是static的静态初始化块,还是static的静态方法块,里面都不能出现this和super的关键字,这个道理很简单:static块是Class域的,就是说static块里面的内容是在JVM的类加载器第一次加载类的时候就被初始化了,而且整个过程只会初始化一次。而this和super都是Object域的操作,this指代的是实例本身,而不是类,同样的super指代的是父类的实例本身,也不是类,所以this和super是不能够出现在static的静态方法块里面的。其实从概念上讲,static和this、super操作的对象不一样,static是属于Class的,而super和this是属于Object的,所以编程过程必须注意这点。

Java Object 对象拷贝答疑的更多相关文章

  1. Java Object 对象拷贝

    Java Object 对象拷贝 @author ixenos JAVA 对象拷贝 Java里的clone分为:  1.浅拷贝:浅复制仅仅复制所考虑的对象,而不复制它所引用的对象,Object类里的c ...

  2. Java Object 对象创建的方式 [ 转载 ]

    Java Object 对象创建的方式 [ 转载 ] @author http://blog.csdn.net/mhmyqn/article/details/7943411 显式创建 有4种显式地创建 ...

  3. Java中对象拷贝的两种方式

    引用的拷贝 //引用拷贝 private static void copyReferenceObject(){ Person p = new Person(23, "zhang") ...

  4. 通过与C++程序对比,彻底搞清楚JAVA的对象拷贝

    目录 一.背景 二.JAVA对象拷贝的实现 2.1 浅拷贝 2.2 深拷贝的实现方法一 2.3 深拷贝的实现方法二 2.3.1 C++拷贝构造函数 2.3.2 C++源码 2.3.3 JAVA通过拷贝 ...

  5. java bean对象拷贝

    Java的bean的属性复制,大家可以都看一下. 谈谈Java开发中的对象拷贝http://www.wtnull.com/view/2/e6a7a8818da742758bcd8b73d49d6be2 ...

  6. JVM系列之:详解java object对象在heap中的结构

    目录 简介 对象和其隐藏的秘密 Object对象头 数组对象头 整个对象的结构 简介 在之前的文章中,我们介绍了使用JOL这一神器来解析java类或者java实例在内存中占用的空间地址. 今天,我们会 ...

  7. java 的对象拷贝(有深浅拷贝两种方式,深拷贝实现的两种方式(逐层实现cloneable接口,序列化的方式来实现))

    Java提高篇--对象克隆(复制)(转自:http://www.cnblogs.com/Qian123/p/5710533.html#_label0)   阅读目录 为什么要克隆? 如何实现克隆 浅克 ...

  8. java Object对象的clone方法

    参考copy链接:http://blog.csdn.net/bigconvience/article/details/25025561 在看原型模式,发现要用到clone这个方法,以前和朋友聊过,没怎 ...

  9. 重写Java Object对象的hashCode和equals方法实现集合元素按内容判重

    Java API提供的集合框架中Set接口下的集合对象默认是不能存储重复对象的,这里的重复判定是按照对象实例句柄的地址来判定的,地址相同则判定为重复,地址不同不管内容如何都判定为不重复,这有时与需求不 ...

随机推荐

  1. HDU-------An Easy Task

    An Easy Task Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total ...

  2. php强制下载文件并显示原始文件名

    原来一直没有接触过,这几天一直在玩儿文件上传下载的东西.今天又遇到一个坑. 描述:文件上传至服务器后,如果是rar或则其他的非浏览器直接识别的格式,用户点击链接了后是可以直接就被下载下来的.那么如果上 ...

  3. Unity3DGUI:人物能量条

  4. hdu 3507 Print Article(斜率优化DP)

    题目链接:hdu 3507 Print Article 题意: 每个字有一个值,现在让你分成k段打印,每段打印需要消耗的值用那个公式计算,现在让你求最小值 题解: 设dp[i]表示前i个字符需要消耗的 ...

  5. [SOJ] DAG?

    Description 输入一个有向图,判断该图是否是有向无环图(Directed Acyclic Graph). Input 输入的第一行包含两个整数n和m,n是图的顶点数,m是边数.1<=n ...

  6. JQuery笔记(三)选项卡

    通过jq封装的方法,可以更简单的制作一个选项卡 <!DOCTYPE html> <html lang="en"> <head> <meta ...

  7. centos7内核升级

    默认centos7的内核版本是3.10,升级的原因是为了测试openvswitch的vlan技术,默认openvswitch的2.3版本是允许centos7默认内核3.10支持,下面是软件与内核版本对 ...

  8. HTML canvas图像裁剪

    canvas drawImage方法的图像裁剪理解可能会比较耗时,记录一下,以便供人翻阅! context.drawImage(img,sx,sy,swidth,sheight,x,y,width,h ...

  9. Angular this vs $scope $event事件系统

    this vs $scope ------------------------------------------------------------------------------ 'this' ...

  10. Users is not mapped(Hibernate实体类采用注解)

    今天做简单的登陆验证web应用时,用HQL语句查询数据表时 总是出现Users is not mapped [from Users u where u.username=? and u.passwor ...