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

原文地址:https://www.cnblogs.com/zuotongbin/p/11723346.html

时间: 2024-10-11 06:00:40

Java高级特性:clone()方法的相关文章

详解Java中的clone方法

转载自:http://blog.csdn.net/zhangjg_blog/article/details/18369201 Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言中,有几种方式可以创建对象呢? 1 使用new操作符创建一个对象 2 使用clone方法复制一个对象 那么这两种方式有什么相同和不同呢? new操作符的本意是

java高级特性之IO流

缓冲流 转换流 对象流 打印流 标准输入输出流 随机访问流 数组流 有关flush():所有的处理流的输出流,最外层流需要刷新. javaIO流 1认识File类 File类的对象表示一个文件或者一个文件目录 绝对路径:包含盘符的文件完整路径 相对路径:在当前路径下的文件路径 File类中的方法,涉及到文件或文件目录的新建.删除.获取文件的路径.获取文件的大小.并没有涉及到向文件中写入或读出内容.这样的读取或写入的功能就需要IO流来完成 一般通过将File类的对象作为参数传递到流的构造器中,作为

【java】克隆clone()方法和相等equals()方法的重写

1.为什么要重写clone()方法? Java中的浅度复制是不会把要复制的那个对象的引用对象重新开辟一个新的引用空间,当我们需要深度复制的时候,这个时候我们就要重写clone()方法. 2.equals()和clone()方法重载的示例 Hourse类: import java.util.Date; public class House implements Cloneable { private int id; private double area; private Date whenBul

详解Java中的clone方法:原型模式

Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言中,有几种方式可以创建对象呢? 1 使用new操作符创建一个对象2 使用clone方法复制一个对象 那么这两种方式有什么相同和不同呢? new操作符的本意是分配内存.程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间.分配完内存之

JAVA中的clone方法剖析

原文出自:http://blog.csdn.net/shootyou/article/details/3945221 java中也有这么一个概念,它可以让我们很方便的“制造”出一个对象的副本来,下面来具体看看java中的Clone机制是如何工作的?     1. Clone&Copy     假设现在有一个Employee对象,Employee tobby =new Employee(“CMTobby”,5000),通常我们会有这样的赋值Employee cindyelf=tobby,这个时候只

转:Java中的Clone()方法详解

Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言中,有几种方式可以创建对象呢? 使用new操作符创建一个对象 使用clone方法复制一个对象 那么这两种方式有什么相同和不同呢? new操作符的本意是分配内存.程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间.分配完内存之后,再

Java中的clone方法详解

Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言中,有几种方式可以创建对象呢? 1 使用new操作符创建一个对象 2 使用clone方法复制一个对象 那么这两种方式有什么相同和不同呢? new操作符的本意是分配内存.程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间.分配完内存

Java高级特性—JVM

1).java监控工具使用 jconsole是一种集成了上面所有命令功能的可视化工具,可以分析jvm的内存使用情况和线程等信息 visualvm 提供了和jconsole的功能类似,提供了一大堆的插件. 插件中,Visual GC(可视化GC)还是比较好用的,可视化GC可以看到内存的具体使用情况. 2).Java虚拟机运行时数据区域划分 被分为五个区域:堆(Heap).栈(Stack).本地方法栈(Native Stack). 方法区(Method Area).程序计数器(Program Cou

Java数组调用clone()方法,返回的对象是深拷贝还是浅拷贝?

结论: 一维数组深拷贝(重新分配内存,并复制值) 二维数组浅拷贝(只传递引用) 注:若要实现二维数组的深拷贝,可以把二维数组内的每个数组分别用clone()方法复制. 原文地址:https://www.cnblogs.com/alyiacon/p/12543122.html