`

转:java.lang.Object.clone()分析

阅读更多

文章转载自:http://www.cnblogs.com/gw811/archive/2012/10/07/2712252.html

 

首先,看一下源码:

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

 由源代码我们会发现:

  第一: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中对象的克隆

  1)在派生类中实现Cloneable借口。

  2)为了获取对象的一份拷贝,我们可以利用Object类的clone方法。

  3)在派生类中覆盖积累的clone方法,声明为public。

  4)在派生类的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;
    Professor pro;
    public Student(){}
    public Student(String name,int age,Professor pro){
        this.name=name;
        this.age=age;
        this.pro=pro;
    }
    public Object clone(){
        Student o=null;
        try {
            //Object中的clone()识别出你要复制的是哪一个对象。
            o=(Student)super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        o.pro=(Professor)pro.clone();
        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 implements Cloneable{
    private String name;
    private int age;
    public Professor(){}
    public 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;
    }
    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;
    }
}
public class StudentTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Professor p=new Professor("wangwu",50);
        Student s1=new Student("zhangsan",18,p);
        Student s2=(Student)s1.deepClone();
        s2.getPro().setName("maer");
        s2.getPro().setAge(40);
        System.out.println("name="+s1.getPro().getName()
                +",age="+s1.getPro().getAge());
        //name=wangwu,age=50
    }
}

 

继续深究:{一下是个人未能想明白的一些问题,网络上也没能给出很好的解释}
  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方法,这又是怎么回事?

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

java.lang.CloneNotSupportedException: com.clone.Dog
     at java.lang.Object.clone(Native Method)
     at com.clone.Dog.main(Dog.java:15)
Exception in thread "main" java.lang.NullPointerException
     at com.clone.Dog.main(Dog.java:20)

 

 

分享到:
评论

相关推荐

    javabiginteger源码-Java:Java

    ##-----java.lang.Object类----- 其equals与==有没有差别? 其hashCode及clone前面有个什么修饰语? 其toString()返回什么? 其构造函数及finalize()做了什么? ##-----java.lang.Class类----- 5. 为什么说Class类不...

    bigcoder84#study-notes#_4Object中的clone方法1

    Object类中的clone方法clone()方法的作用克隆方法用于创建对象的拷贝,为了使用clone方法,类必须实现java.lang.Cloneable接口

    java深入理解浅拷贝和深拷贝

    拷贝对象是java中经常会遇到的问题。...java中所有的对象都是继承自java.lang.Object。Object对象中提供了一个clone方法,来供我们对java对象进行拷贝。 protected native Object clone() throws CloneNot

    Java 面试宝典

    21、写 clone()方法时,通常都有一行代码,是什么? ............................................. 16 22、面向对象的特征有哪些方面 ...........................................................................

    java 面试题 总结

    java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类 3、int 和 Integer 有什么区别 Java 提供两种不同的类型:引用类型和原始类型(或内置...

    servlet2.4doc

    Overrides the standard java.lang.Object.clone method to return a copy of this cookie. containsHeader(String) - Method in class javax.servlet.http.HttpServletResponseWrapper The default behavior of ...

    jsp内置对象的用法

    page对象就是指向当前JSP页面本身,有点象类中的this指针,它是java.lang.Object类的实例 序号 方 法 说 明 1 class getClass 返回此Object的类 2 int hashCode() 返回此Object的hash码 3 boolean equals...

    java面试题大全(2012版)

    82、能不能自己写个类,也叫java.lang.String? 57 83. Java代码查错 57 二. 算法与编程 61 1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt...

    java面试宝典2012版.pdf

    Java面试宝典2012版 一. Java基础部分 1、一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 2、Java有没有goto?...82、能不能自己写个类,也叫java.lang.String? 83. Java代码查错

    Java面试宝典-经典

    82、能不能自己写个类,也叫java.lang.String? 57 83. Java代码查错 57 二. 算法与编程 61 1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt...

    超级有影响力霸气的Java面试题大全文档

     java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类 6、int 和 Integer 有什么区别  Java 提供两种不同的类型:引用类型和原始类型(或...

    最新Java面试宝典pdf版

    82、能不能自己写个类,也叫java.lang.String? 57 83. Java代码查错 57 二. 算法与编程 61 1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt...

    knowledge-interview:知识学习

    都在java.lang 包下 String使用final修饰,不可变,每次字符串变更都是创建新的字符串,String s = “a”+"b"除外,频繁变更会导致无引用对象过多,导致gc影响性能。 StringBuffer是线程安全的,每次变更是对本身...

    Java面试笔试资料大全

    82、能不能自己写个类,也叫java.lang.String? 57 83. Java代码查错 57 二. 算法与编程 61 1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt...

    java面试宝典2012

    82、能不能自己写个类,也叫java.lang.String? 62 83. Java代码查错 63 二. 算法与编程 67 1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt...

Global site tag (gtag.js) - Google Analytics