zoukankan      html  css  js  c++  java
  • Java高级特性:clone()方法

    源码

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

    由源码可知。

    • 第一:Objcet类的clone()方法是一个native方法。native方法的执行效率一般远高于Java中的非native方法(一般不是java语言所写)。这也解释了为什么要用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修饰符修饰。(关于protected修饰符见我的其它文章。)这意味着clone()方法只对java.util.lang包可见,和继承了Object类的子类可见。当然所有类都是继承了Object类。
      • 为什么要用protected修饰呢?
        为了安全,安全性从两方面考虑。首先我们要clone一个Employee对象,应该只有Employee类能够克隆Employee对象。使用protected修饰符保证了其它不相关的类无法克隆Employee对象。其次是Object对要复制的对象一无所知,它只会逐域进行复制,如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题。但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样原对象和克隆的对象还会共享一些信息。所以默认的拷贝都是浅拷贝。要想在任何地方都使用clone方法,我们必须将它改为public类型,并且实现Cloneable接口。如果一开始就是public,我们就分不清到底是浅拷贝还是深拷贝了。使用protected和Cloneable就约束了类设计者要了解自己的克隆过程。
        Cloneable接口是标记接口,它不包含任何需要实现的方法。如果一个对象请求克隆,但没有实现这个接口,就会产生一个受检查异常,因为clone方法默认实现中有使用instanceof进行接口判断。
        这里给出代码
    //cloneTest.java
    package com.testbase.clone;
    
    public class cloneTest {
        public static void main(String[] args){
            // 虽然我们已经实现了Cloneable接口,不会产生异常
            // 但是编译器并不知道,会报错,所以这里要捕获异常
            try
            {
                Employee tobin = new Employee(30000);
                int salary = tobin.getSalary();
                System.out.println(salary);
                Employee shengsheng = tobin.clone();
                int shengSalary = shengsheng.getSalary();
                System.out.println(shengSalary);
            }
            catch (CloneNotSupportedException e)
            {
                e.printStackTrace();
            }
        }
    }
    //Employee.java
    package com.testbase.clone;
    
    public class Employee implements Cloneable
    {
        private int salary;
        public Employee()
        {
    
        }
        public Employee(int asalary)
        {
            salary = asalary;
        }
        @Override
        public Employee clone() throws CloneNotSupportedException
        {
            Employee cloned = (Employee) super.clone();
            return cloned;
        }
    
        public int getSalary() {
            return salary;
        }
    }
    
    
    • 第三:Object.clone()方法返回一个Object对象。我们必须进行强制转换才能得到我们需要的类型。(强制转换一定能成功吗?如果重写了clone方法一定成功。但是如果没有重写,不能转换。clone的是父类Object,无法向下造型,子类重写了clone方法,拷贝就是当前类的对象,暂时转为Object,还可以通过强制类型转换回来)
      给出一段错误代码。在Employee.java中拷贝是因为在其它类中拷贝一定不成功。因为Object类clone方法的protected特性。只有继承了它的子类和java.util.lang包可以调用clone()方法。所以选择在子类Employee中测试。
    package com.testbase.clone;
    
    public class Employee implements Cloneable
    {
        private int salary;
        public Employee()
        {
    
        }
        public Employee(int asalary)
        {
            salary = asalary;
        }
    //    @Override
    //    public Employee clone() throws CloneNotSupportedException
    //    {
    //        Employee cloned = (Employee) super.clone();
    //        return cloned;
    //    }
    
        public int getSalary() {
            return salary;
        }
    
        public static void main(String[] args){
            try
            {
                Employee tobin = new Employee(30000);
                int salary = tobin.getSalary();
                System.out.println(salary);
                Object shengsheng = tobin.clone(); //这里不能强制类型转换
                int shengSalary = shengsheng.getSalary();//这里报错,因为Object没有getSalary方法
                System.out.println(shengSalary);
            }
            catch (CloneNotSupportedException e)
            {
                e.printStackTrace();
            }
        }
    }
    

    深拷贝和浅拷贝

    浅拷贝:拷贝引用,但是不拷贝引用指向的对象,对拷贝引用的对象进行修改,两份拷贝都会被修改。如果源对象和浅拷贝对象所共享的子对象都是不可变的,那么这种共享就是安全的。比如String类。或者在对象的生命周期内,子对象一直包含着不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况同样是安全的。
    深拷贝:可能会更改的子对象也进行了拷贝。要进行深拷贝,需要一层层地重写clone方法。

    • 数组是深拷贝。所有数组类型都有一个public的clone()方法,可以用这个方法建立一个新数组。
    int[] a = {1,2,3,4};
    int[] cloned = a.clone();
    cloned[0]=11;
    

    对象串行化实现拷贝

    常见面试题

    1.为什么进行拷贝

    • 因为我们某个对象实例现在需要保存一些有效值,我们希望生成一个和原来一样的对象,对这个对象的修改不改变原对象的属性。

    2.深拷贝和浅拷贝的区别

    • 浅拷贝对基本数据类型生成一份新的拷贝,对引用类型,只是复制了引用,引用所指向的那块内存空间并没有拷贝
    • 深拷贝对引用指向的那块内存空间也拷贝了一份(新内存空间)。换言之深拷贝要把复制的对象所引用的对象也都拷贝了一遍。

    3.String克隆的特殊性在那里?StringBuffer和StringBuilder呢?

    • 对基本数据类型都能自动实现深拷贝。而对引用类型是浅拷贝。String是引用类型的特例。因为String是不允许修改的。所以相当于进行了深拷贝,是安全的。由于String是不可变类,对String类中的很多修改操作都是通过new对象复制处理的。所以当我们修改clone前后对象里面String属性的值时,实际上就指向了新的内存空间。自然对clone前的源对象没有影响,类似于深拷贝。虽然它是引用类型,但是不影响我们深拷贝的使用。
      而对于StringBuffer和StringBuilder,需要主动进行clone重写。否则就是浅拷贝。

    4.实现对象克隆的常见方式有哪些,具体怎么做?
    常用的方式有三种。

    • 通过自己写一个clone方法,new一个同样的对象,赋值实现深度克隆,繁琐容易出错。
    • 通过实现Cloneable接口并重写Object类的clone方法,分为深浅两种方式。
    • 通过Serializable接口并用对象的序列化和反序列化来实现真正的深拷贝。

    代码,主要是第2和第3个方法。
    通过实现 Cloneable 接口并重写 Object 类的 clone() 方法实现浅克隆做法:Object 类中 clone 方法的默认实现最终是一个 native 方法(如果 clone 类没有实现 Cloneable 接口并调用了 Object 的 clone 方法就会抛出 CloneNotSupportedException 异常,因为 clone 方法默认实现中有使用 instanceof 进行接口判断),相对来说效率高些,默认实现是先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容,对基本数据类型就是值复制,而对非基本类型变量保存的仅仅是对象的引用,所以会导致 clone 后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
    浅拷贝。

    class Employee implements Clonealbe{
        public Employee clone() throws CloneNotSupportedException
        {
            return (Employee) super.clone();
        }        
    }
    

    深拷贝

    class Employee implements Clonealbe{
        public Employee clone() throws CloneNotSupportedException
        {
            Employee cloned = (Employee) super.clone();
            cloned.hireDay = (Date) hireDay.clone();
            return cloned();
        }        
    }
    

    通过Serializable接口并用对象的序列化和反序列化来实现真正的深拷贝。(还未学到)

    class CloneUtil {  
       public static <T extends Serializable> T clone(T obj) {  
           T cloneObj = null;
           try {
               ByteArrayOutputStream byteOut = new ByteArrayOutputStream();  
               ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
               objOut.writeObject(obj);  
               objOut.close();  
               ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); 
               ObjectInputStream objIn = new ObjectInputStream(byteIn);  
               cloneObj = (T) objIn.readObject();  
               objIn.close();
           } catch (IOException e) {
               e.printStackTrace();
           } catch (ClassNotFoundException e) {
               e.printStackTrace();
           }  
           return cloneObj;  
       }
    }
     
     
    class InfoBean implements Serializable {  
       public String name;
       public int age;
    }
     
     
    class PeopleBean implements Serializable {  
       public String vipId;
       public InfoBean infoBean;
     
       public Object clone() {
           return CloneUtil.clone(this);
       }  
    }
    

    参考文章:
    https://www.cnblogs.com/gw811/archive/2012/10/07/2712252.html
    https://blog.csdn.net/qq_26857649/article/details/84316081

  • 相关阅读:
    禁止Crontab产生邮件
    centos6.2 ftp 配置
    [转] 夏天折T恤的方法
    [转] 55个经典开源Windows工具(2)
    [转] 55个经典开源Windows工具(3)
    [**轻松一下**] 程序员喝酒喝出的计算机文化!
    [转] 55个经典开源Windows工具(1)
    [转] 关于mscomm的用法,提高篇
    [转] 传说中的减肥............
    [转] 关于MSCOMM控件的一些说明
  • 原文地址:https://www.cnblogs.com/zuotongbin/p/11723346.html
Copyright © 2011-2022 走看看