java.lang.Object.clone()分析

文章来源:http://www.cnblogs.com/gw811/archive/2012/10/07/2712252.html


要点总结
1.要使用object的clone方法的前提:
a)子类要实现Cloneable接口,否则调用clone()方法,会抛CloneNotSupportedException
b)子类要重写clone()方法,然后修改包访问权限为public
c)在clone()方法中要调用super.clone()方法,是实现复制的核心
d)clone()方法,返回的是Object类型,自己要注意类型转换

2.清楚什么是浅层复制?什么是深层复制
具体看文章分析,有讲
3.执行clone(),默认是浅层复制还是深层复制?
答:默认是浅层复制
4.执行clone()方法,要实现深层复制,该怎么做?在代码层面上深层复制比浅层复制多增加了什么?
答:1)在成员属性字段中,所有引用的对象都要实现Cloneable接口,并重写clone()方法。在要实现深层复制的类的clone()方法中,要手动调用引用对象的clone(),然后将返回的副本手动注入到要主对象的对应成员字段中。2)对引用对象还要进行clone
5.通过序列化来实现深层复制,该怎么做?
答:实现序列化Serializable接口,将对象写入流中,再从流中取出,注意取出的对象是原对象的拷贝。而且还是深层拷贝
6.java中对数组进行clone(),默认是浅层复制
7.String对象不能调用clone()方法
答:因为String对象没有实现Cloneable接口,也没有重写clone()方法。所以不能调用。这也与String对象不可变的特性相对应。如果要对String对象进行浅拷贝可以通过String a=”abc”;String b=a;这也来达到效果。如果要对String对象进行深拷贝可以通过String a=”abc”;String b=new String(a);来达到相同的效果

首先,看一下源码:

1 public class Object  {
2     protected native Object clone() throws CloneNotSupportedException;
3 }

  由源代码我们会发现:

  第一:Object类的clone()方法是一个native方法,native方法的效率一般来说都是远高于Java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息复制到新对象中,虽然这也实现了clone功能。(JNI是Java Native Interface的 缩写。从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。)

  第二:Object类中的 clone()方法被protected修饰符修饰。这也意味着如果要应用 clone()方 法,必须继承Object类,在 Java中所有的类是缺省继承 Object类的,也就不用关心这点了。然后重载 clone()方法。还有一点要考虑的是为了让其它类能调用这个 clone类的 clone()方法,重载之后要把 clone()方法的属性设置为 public。

  第三:Object.clone()方法返回一个Object对象。我们必须进行强制类型转换才能得到我们需要的类型。

浅层复制与深层复制概念:

浅层复制
被复制的对象的所有成员属性都有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅层复制仅仅复制的是成员的值,而不复制它所引用的对象,对于引用的对象只是复制的指向堆中的地址。(概念不好理解,请结合下文的示例去理解)

深层复制:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不是原有的那些被引用的对象。换言之,深层复制要复制的对象引用的对象都复制一遍。

  Java中对象的克隆

  • 在派生类中实现Cloneable借口。
  • 为了获取对象的一份拷贝,我们可以利用Object类的clone方法。
  • 在派生类中覆盖积累的clone方法,声明为public。
  • 在派生类的clone方法中,调用super.clone()。


  实现Cloneable接口

  首先,看一下源码:  

public interface Cloneable { 
 }

  我们奇怪的发现Cloneable竟然是空的,那么我们为什么要实现Cloneable接口呢?其实Cloneable接口仅仅是一个标志,而且这个标志也仅仅是针对 Object类中 clone()方法的,如果 clone 类没有实现 Cloneable 接口,并调用了 Object 的 clone() 方法(也就是调用了 super.Clone() 方法),那么Object 的 clone() 方法就会抛出 CloneNotSupportedException 异常。

  程序示例分析

public class Person {
    private String name;
    private int age;
    public Person(){}
    public Person(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;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
public class PersonTest {
    public static void main(String[] args) {
        Person p1=new Person("zhangsan",18);
        Person p2=(Person)p1.clone();
        p2.setName("lis");
        p2.setAge(20);
        System.out.println("name="
            +p1.getName()+",age="+p1.getAge());
        //修改p2后,没有对p1产生影响。
    }
}

  说明:

  1)为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的clone()识别你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。

  2)继承自java.lang.Object.clone()方法是浅层复制。以下代码可以证明之:

public class Student implements Cloneable {
    private String name;
    private int age;
    private Professor pro;
    public Student(){}
    public Student(String name,int age,Professor pro){
        this.name=name;
        this.age=age;
        this.pro=pro;
    }
    public Object clone(){
        Object o=null;
        try {
            //Object中的clone()识别出你要复制的是哪一个对象。
            o=super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Professor getPro() {
        return pro;
    }
    public void setPro(Professor pro) {
        this.pro = pro;
    }
}
class Professor{
    private String name;
    private int age;
    public Professor(){}
    public Professor(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
public class StudentTest {
    public static void main(String[] args) {
        Professor p=new Professor("wangwu",50);
        Student s1=new Student("zhangsan",18,p);
        Student s2=(Student)s1.clone();
        s2.getPro().setName("maer");
        s2.getPro().setAge(40);
        System.out.println("name="+s1.getPro().getName()
                +",age="+s1.getPro().getAge());
        //name=maer,age=40
    }
}

  那么我们如何实现深层复制的克隆,即在修改s2.Professor时不影响s1.Professor?代码改进如下:

public class Student implements Cloneable {
    private String name;
    private int age;
    private Professor pro;
    public Student(){}
    public Student(String name,int age,Professor pro){
        this.name=name;
        this.age=age;
        this.pro=pro;
    }
    public Object clone(){
        Object o=null;
        try {
            //Object中的clone()识别出你要复制的是哪一个对象。
            o=super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Professor getPro() {
        return pro;
    }
    public void setPro(Professor pro) {
        this.pro = pro;
    }
}
class Professor{
    private String name;
    private int age;
    public Professor(){}
    public Professor(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

public class StudentTest {
    public static void main(String[] args) {
        Professor p=new Professor("wangwu",50);
        Student s1=new Student("zhangsan",18,p);
        Student s2=(Student)s1.clone();
        s2.getPro().setName("maer");
        s2.getPro().setAge(40);
        System.out.println("name="+s1.getPro().getName()
                +",age="+s1.getPro().getAge());
        //name=wangwu,age=50
    }
}

利用串行化来实现深层复制

  把对象写到流中的过程是串行化(Serilization)过程,而把对象从流中读出来是并行化(Deserialization)过程。应当指出的是,写在流中的是对象的一个拷贝,而原来对象仍然存在JVM里面。

  在Java语言里深层复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流中,再从流中读出来,便可以重建对象。

  这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象是否设成transient,从而将之排除在复制过程之外。代码改进如下:

public class Student implements Serializable {
    private String name;
    private int age;
    Professor pro;
    public Student(){}
    public Student(String name,int age,Professor pro){
        this.name=name;
        this.age=age;
        this.pro=pro;
    }
    public Object deepClone() throws IOException, ClassNotFoundException{
        //将对象写到流中
        ByteArrayOutputStream bo=new ByteArrayOutputStream();
        ObjectOutputStream oo=new ObjectOutputStream(bo);
        oo.writeObject(this);
        //从流中读出来
        ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi=new ObjectInputStream(bi);
        return oi.readObject();
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Professor getPro() {
        return pro;
    }
    public void setPro(Professor pro) {
        this.pro = pro;
    }
}
class Professor implements Serializable{
    private String name;
    private int age;
    public Professor(){}
    public Professor(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

  继续深究:
  1、数组:(以int[]为例):

public class ArrayClone {
    public static void main(String[] args) {
        int[] a1={1,2,3,4};
        int[] a2=a1.clone();
        System.out.println(Arrays.toString(a2));
        //[1, 2, 3, 4]
        String[] s1={"hello","china"};
        String[] s2=s1.clone();
        System.out.println(Arrays.toString(s2));
        //[hello, china]
        Object[] o1={new Object(),new Object()};
        Object[] o2=o1.clone();
        System.out.println(Arrays.toString(o2));
        //[java.lang.Object@1fc4bec, java.lang.Object@dc8569]
    }
}

  我们发现Java数组有clone()方法,而且不需要我们去进行强制类型转换,Java底层是怎样实现数据结构这个功能的?

public class ArrayClone {
    public static void main(String[] args) {
        Person p1=new Person("wangwu",18);
        Person p2=new Person("lisi",28);
        Person[] ps1={p1,p2};
        Person[] ps2=ps1.clone();
        ps2[0].setName("wanghao");
        ps2[0].setAge(22);
        System.out.println("name="+p1.getName()+",age="+p1.getAge());
        //name=wanghao,age=22
    }
}

复制代码
  由测试可知,Java数组只具备浅层复制的功能。

  2、String类

public class StringClone {
    public static void main(String[] args) {
        String str1="wang";
        //String str2=(String)str1.clone();
        //编译错误,String类没有clone方法
    }
}

复制代码
  查看源代码,我们可知,String类并没有重载Object类的clone方法。虽然,String和Object都在java.lang包中,但是我们的测试类StringClone不在java.lang包中,因此,str.clone()时会出现编译错误。继续进行:

public class Dog {
    private String name;
    private int age;
    public Dog(){}
    public Dog(String name,int age){
        this.name=name;
        this.age=age;
    }
    public static void main(String[] args) {
        Dog dog1=new Dog("dog1",5);
        Dog dog2=null;
        try {
            dog2=(Dog)dog1.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(dog2.getName()+","+dog2.getAge());
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

  我们惊奇的发现,dog1.clone();并没有出现变异错误,我们随便创建的类具有clone方法,这又是怎么回事?

  虽然没编译错误,但是运行时出错,如下所示:

1 java.lang.CloneNotSupportedException: com.clone.Dog 2 at
java.lang.Object.clone(Native Method) 3 at
com.clone.Dog.main(Dog.java:15) 4 Exception in thread “main”
java.lang.NullPointerException 5 at
com.clone.Dog.main(Dog.java:20)

文章来源:http://www.cnblogs.com/gw811/archive/2012/10/07/2712252.html

原文地址:https://www.cnblogs.com/chenny3/p/10226132.html