java对象的克隆

来源:互联网 发布:末代皇帝 知乎 编辑:程序博客网 时间:2024/06/04 17:46

对象的拷贝[深拷贝和浅拷贝]

  i.关于clone[对象拷贝]——在实际编程过程,有时候我们会遇到一种情况:当你有一个对象A,在某一个时刻,A已经保存了对应的属性值,而且这些值本身是有效的,这个时候可能需要一个和A完全相同的对象B,并且当B里面的属性值发生变化的时候,A中的属性值不受影响,可以理解为AB独立,但是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,这样就成功完成了对象的拷贝过程。若你对上边这段代码还有不了解的地方可以尝试将下边的代码修改掉:

AClass class2 = (AClass)class1.clone() 修改为:AClass class2 = class1

  改了过后,输出结果应该为:

12

true

  所以在对象的clone过程,需要注意的几点有:

  [1]希望能够提供对象clone功能的类必须实现Cloneable接口,这个接口位于java.lang包里面

  [2]希望提供对象clone功能的类必须重载clone()方法,在重载过程可以看到这句话:super.clone();也就是说,不论clone类的继承结构如何,我们在对象拷贝的时候都直接或简介调用了Objectclone()方法。而且细心留意可以看到Objectclone方法是protected域的,也就是说这个方法只有Object的子类可以调用,而在重载的时候将clone方法修饰符改为public

  [3]还有一点很重要就是Object源代码里面的clone()方法是native方法,一般而言,对JVM来说,native方法的效率远比java中的普通方法高,这就是为什么我们在复制一个对象的时候使用Objectclone()方法,而不是使用new的方式。

  [4]Cloneable接口和我们在编写IO程序的时候序列化接口一样,只是一个标志,这个接口是不包含任何方法的,这个标志主要是为了检测Object类中的clone方法,若我们定义的类想要实现拷贝功能,但是没有实现该接口而调用Objectclone方法,那么就会出现语句中catch块里面的异常错误,抛出CloneNotSupportedException

  ii浅拷贝——在对象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里面的objchange方法,所以不应该修改class1里面的obj里面的变量a的值【初衷】

22

A Value is 23

  不知细心的读者有没有发现输出和我们预期的拷贝不一样,虽然class2引用的对象是从class1拷贝过来的,class2里面的引用objclass1里面的引用obj实际上还是指向了同一个对象,其含义在于,拷贝的初衷是要复制一个一模一样的对象,包括对象里面的对象也应该实现的是复制操作,它最终的目的是保证class1class2本身的属性以及class1class2里面的对象引用的属性在拷贝过后的各种相关操作里面相互独立,上边输出证明了class1class2里面的变量a确实已经拷贝成功,但是class1class2里面的AClass对象的引用obj在拷贝过后还是指向了同一个对象,所以拷贝结束过后,调用class2objchange方法的时候,也修改了class1里面的obj指向的对象里面的值。所以在Java里面我们把上边的拷贝过程称为浅拷贝,同样又称为影子clone”

从这里可以知道,在JVM的对象复制里面,实际上基本数据类型可以直接通过这种方式来进行拷贝工作,而非原始类型这样操作了过后拷贝的对象仅仅拷贝了对象里面的基本数据类型的成员变量,而比较复杂的类型的成员变量并没有像预期一样产生拷贝效果,这种拷贝我们就称之为浅拷贝

ii.深拷贝——如果要实现我们预期的对象拷贝效果,就需要使用深拷贝操作,其实在浅拷贝基础上实现深拷贝有两个步骤,以上边的代码为例:

  [1]第一步:AClass实现同样的clone功能

  [2]第二步:在BClassclone操作中多写入一句话:object.obj = (AClass)obj.clone();

 

 

 

 

 

 

 

 

对象克隆

  在java面向对象的编程当中,要复制引用类型的对象,就必须克隆这些对象。通过调用对所有引用类型和对象都是可用的clone方法,来实现克隆。
  如果是值类型的实例,那么=赋值运算符就可以将源对象的状态逐字节地复制到目标对象中。

要点

  1> 为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
  2> 派生类中覆盖基类的clone(),并声明为public
  3> 在派生类的clone()方法中,调用super.clone()
  4> 在派生类中实现Cloneable接口。
  5>Cloneable是一个没有抽象方法的标识接口。

建立一个本地拷贝

  给一个对象建立本地拷贝的原因很可能是由于您计划修改该对象,并且您不想修改方法调用者的对象。假如您确定您需要一个本地拷贝,您能够使用Object类的clone()方法来执行这项操作。clone()方法被定义为受保护方法,但您必须在您希望克隆的任何子类中重新公开定义他。
  例如,标准库类ArrayList忽略clone(),但您能够这样为ArrayList调用clone()方法:
  import java.util.*;class MyInt {
  private int i;
  public MyInt(int ii) { i = ii; }
  public void increment() { i++; }
  public String toString() {
  return Integer.toString(i);
  }
  }public class Test {
  public static void main(String[] args) {
  ArrayList al = new ArrayList();
  for(int i = 0; i < 10; i++ )
  al.add(new MyInt(i));
  ArrayList al1 = (ArrayList)al.clone();
  // Increment all al1&apos;s elements:
  for(Iterator e = al1.iterator(); e.hasNext(); )
  ((MyInt)e.next()).increment();}
  }
  clone()方法生成一个Object,他必须重新转变为适当的类型。这个例子说明ArrayListclone()方法如何不能自动克隆ArrayList包含的每一个对象——原有ArrayList和克隆后的ArrayList是相同对象的别名。
  这种情况通常叫做浅拷贝,因为他仅仅复制一个对象的表面部分。实际的对象由这个表面,引用指向的任何对象,连同那些对象指向的任何对象等构成。这往往被称作对象网络。假如您拷贝任何这些内容,则被称为深拷贝。例如:
  class DeeplyClone
  {
  public static void main(String[] args)
  {
  Professor p=new Professor("feiyang",23);
  Student s1=new Student("zhangshan",18,p);
  Student s2=(Student)s1.clone();
  s2.p.name="Bill.Gates";
  s2.p.age=30;
  System.out.println("name="+s1.p.name+","+"age="+s1.p.age);
  }
  }
  class Professor implements Cloneable
  {
  String name;
  int age;
  Professor(String name,int age)
  {
  this.name=name;
  this.age=age;
  }
  public Object clone()
  {
  Object o=null;
  try
  {
  o=super.clone();
  }
  catch(CloneNotSupportedException e)
  {
  e.printStackTrace();
  }
  return o;
  }
  }
  class Student implements Cloneable
  {
  Professor p;
  String name;
  int age;
  Student(String name, int age,Professor p)
  {
  this.name=name;
  this.age=age;
  this.p=p;
  }
  public Object clone()
  {
  //Object o=null;
  Student o=null;
  try
  {
  o=(Student)super.clone();
  }
  catch(CloneNotSupportedException e)
  {
  e.printStackTrace();
  }
  o.p=(Professor)p.clone();
  return o;
  }
  }

例外

  如果为一个不实现cloneable的类调用clone的话,那么就会抛出一个CloneNotSupportedException异常。相对于实现cloneable接口的类来说,如果为使用默认方法Object.clone的类的实例调用clone的话,就必须采取以下动作之一:
  1.clone的调用周围包装一个try代码块并捕捉到CloneNotSupportedException异常。
  2.将异常CloneNotSupportedException添加到调用clone的方法的throws子句中,抛出这个异常。

面向对象

比如说,我们要用程序来描述一个人。如果是以往的结构化编程,我们可能会这样;
例如用C语言的话,可能会建立一个结构体:
struct Person{
  姓名;
  年龄;
  等等;...
}
然后用定义的一些函数来描述他的行为。比如void walk(struct p); void eat(struct p);等等。
然后使用 walk(p) 来描述行走,使用eat(p)来描述吃饭等。
这样不利于程序结构的维护。开发效率也不高。

但是用java面向对象方式,这一切就显得自然了。我们建立一个类,表示人:
class Person{
  姓名
  性别
  体重
  身高等..(人类都有的属性)
  吃饭
  睡觉 等行为(也就是人类都有的行为)
}

然后将类产生一个实例,就产生了一个&apos;&apos;的对象。
Person xiaobai = new Person("小白",身高,体重...等参数);

如果要描述祖先后代的关系,很简单。只要让人类继承自Animal即可。
class Animal{
......
}

class Person extends Animal{
.......
}

这样动物有的属性和行为,人不用定义也可以拥有,符合自然规律~哈哈

面向对象不是java特有的,只是一种思想,如果你愿意,用结构化语言C语言也可以写出面向对象的代码

原创粉丝点击