Java高级特性:clone()方法

Posted 喜欢吃西瓜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于Java高级特性:clone()方法的主要内容,如果未能解决你的问题,请参考以下文章

新年在家学java之基础篇-高级类的特性

java程序-类的高级特性

Java高级特性—反射和动态代理

Java高级特性之注解

MyBatis高级特性

Java克隆(Clone)的应用