Cloneable

语雀链接

1、API

  • 虽然clone方法定义在Object类中,但要正确使用它,我们需要在需要克隆的类中进行适当的设置和重写。
  • 如果某个类的对象要想被克隆,则对象所在的类必须实现Cloneable接口,重写clone方法(来自Object的clone方法),否则报错CloneNotSupportedException
  • Cloneable该接口没有定义任何方法,是一个标记方法
  	这是Java的一个设计缺陷。是个很糟糕的设计。如果Java在今天被重新设计一次的话,多半就不会设计成这样了。
		当时的一些设计需求 / 限制是:
			1 Java对象要支持clone功能。但不是所有Java对象都应该可以clone,而是要让用户自己标记出哪些类是可以clone的
			2 clone()是一个特殊的多态操作,最好是有JVM的直接支持
			3 早期Java不支持annotation。从Java 5开始支持。
			4 早期Java支持接口形式的“声明多继承”
			5 早期Java不支持任何“实现多继承”(简称“多继承”)。从Java 8开始可以通过接口的default method实现。
		把上述几条结合起来,就得到了Cloneable接口这个糟糕的设计。
		首先,我们要能标记出哪些类是可以clone的。在Java里,类型层面的元数据可以用几种方法来表示:
			1 继承的基类
			2 实现的接口
			3 直接在Class文件中通过access flags实现的修饰符
			4 使用annotation,无论是自定义的还是Java自带的

		显然当初设计Java的时候,一个类是否应该支持clone,是一个重要的属性,但却还没重要到值得给它一个关键字修饰符来修饰class声明,于是不能用(3)。
		然后Java类是单继承的,如果要出于标记目的而消耗掉“基类”这个资源,显然是有点别扭的(但想想看倒也不是完全不可以…),所以(1)也不太好。
		那么就只剩下(2)和(4)了。可是早期Java不支持(4),就只剩下(2)了。
		其次,clone()的语义有特殊性,最好是有JVM的直接支持,然后用户代码就算要自定义clone()最好也要调用JVM提供的基础实现然后再添加自己的功能(也就是大家经常简单的在clone()中先调用super.clone()的做法)。
		JVM要直接支持,得在API里找地方来暴露出这个支持给Java代码调用才行啊。最直观的做法就是把clone()方法的基本实现放在一个所有可以clone的类都能访问到的基类中,让可clone的类继承这一实现。
		但根据上面一点的讨论,我们不希望把clone()的基本实现放在一个特殊基类中,消耗掉Java类唯一的“基类”槽。那还能放哪里呢?干脆就放在java.lang.Object这个所有Java类的共通基类上吧。
		所以说一个实现了Cloneable接口的类跟一个没实现该接口的类有啥区别呢?
		从JVM的角度看,这就是一个标记接口而已。实现了就是打上cloneable标记,没实现就是没这个标记。		然后到clone()的基本实现中,JVM会去检测要clone的对象的类有没有被打上这个标记,有就让clone,没有就抛异常。就这么简单。
		Java里的数组类型是由JVM直接实现的,没有对应的Java源码文件。具体JVM如何实现是它们的自由。

2、深拷贝 VS 浅拷贝

2-1、引入传递

  • 下面的例子,p1、p2内存地址一样,无论修改p1还是p2,另一个都会收到影响
public class Test {
    public static void main(String[] args) {

        Person p1 = new Person();
        p1.setName("p1");

        Person p2 = p1;

        // 内存地址一样
        // com.company.Person@74a14482
        System.out.println(p1);
        // com.company.Person@74a14482
        System.out.println(p2);

        p1.setName("xxx");

        // xxx
        System.out.println(p1.getName());
        // xxx
        System.out.println(p2.getName());
    }
}

2-2、浅拷贝

  • 所有的java类都继承的java.lang.Object,Object里面有个native clone()方法
protected native Object clone() throws CloneNotSupportedException;
  • Person.java 添加clone方法,发现上述代码运行会报错,CloneNotSupportedException
public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  • 查看源码可知:native关键字修饰的方法,代表这个方法实现体被调用,是告知 jvm去调用非java代码编写的实现体,例如C语言编写的等。而 jvm能否去调用这个实现体,也就是根据咱们是否有实现了Cloneable这个接口做为标记
public class Person implements Cloneable{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  • 运行结果如下
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {

        Person p1 = new Person();
        p1.setName("p1");

        Person p2 = (Person) p1.clone();

        // 内存地址一样
        // com.company.Person@74a14482
        System.out.println(p1);
        // com.company.Person@1540e19d
        System.out.println(p2);

        p1.setName("xxx");

        // xxx
        System.out.println(p1.getName());
        // p1
        System.out.println(p2.getName());
    }
}

2-3、深拷贝

  • Person中还有一个Age类,Person实现了Cloneable接口,但是Age还没有
public class Person implements Cloneable{
    private String name;

    private Age age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Age getAge() {
        return age;
    }

    public void setAge(Age age) {
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  • 验证结果:Person是不同内存地址,但是Age内存地址一样
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {

        Person p1 = new Person();
        p1.setName("p1");
        Age age = new Age();
        age.setAge(10);
        p1.setAge(age);

        Person p2 = (Person) p1.clone();

        // 内存地址一样
        // com.company.Person@74a14482
        System.out.println(p1);
        // com.company.Person@1540e19d
        System.out.println(p2);

        p1.setName("xxx");
        p1.getAge().setAge(20);

        // xxx
        System.out.println(p1.getName());
        // com.company.Age@677327b6
        System.out.println(p1.getAge());
        // 20
        System.out.println(p1.getAge().getAge());
        // p1
        System.out.println(p2.getName());
        // com.company.Age@677327b6
        System.out.println(p2.getAge());
        // 20
        System.out.println(p2.getAge().getAge());
    }
}
  • Age也实现Cloneable接口,但是上述验证代码Age还是一样的内存地址
public class Age implements Cloneable{
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  • 改造下Person的clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
        person.age = (Age) age.clone();
        return person;
//        return super.clone();
    }
  • 验证结果如下:两个age已经是不同的内存地址了,这里就是深拷贝
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {

        Person p1 = new Person();
        p1.setName("p1");
        Age age = new Age();
        age.setAge(10);
        p1.setAge(age);

        Person p2 = (Person) p1.clone();

        // 内存地址一样
        // com.company.Person@74a14482
        System.out.println(p1);
        // com.company.Person@1540e19d
        System.out.println(p2);

        p1.setName("xxx");
        p1.getAge().setAge(20);

        // xxx
        System.out.println(p1.getName());
        // com.company.Age@677327b6
        System.out.println(p1.getAge());
        // 20
        System.out.println(p1.getAge().getAge());
        // p1
        System.out.println(p2.getName());
        // com.company.Age@14ae5a5
        System.out.println(p2.getAge());
        // 10
        System.out.println(p2.getAge().getAge());
    }
}

2-4、深拷贝 / 浅拷贝

  • 浅拷贝: 指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
  • 深拷贝 :深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
  • 上面的例子2-2里面,其实就是属于浅拷贝,对于clone方法来说,如果不对clone方法进行改造,那么默认的使用,都是浅拷贝。
  • 上面的例子2-3里面,其实就是属于深拷贝

2、源码翻译

package java.lang;

/**
 * A class implements the <code>Cloneable</code> interface to
 * indicate to the {@link Object#clone()} method that it
 * is legal for that method to make a
 * field-for-field copy of instances of that class.
 * 一个类实现Cloneable接口,以指示Object.clone()方法,该方法对于该类的实例进行现场复制是合法的。
 * <p>
 * Invoking Object's clone method on an instance that does not implement the
 * <code>Cloneable</code> interface results in the exception
 * <code>CloneNotSupportedException</code> being thrown.
 * 在不实现Cloneable接口的实例上调用对象的克隆方法导致抛出异常CloneNotSupportedException 。
 * <p>
 * By convention, classes that implement this interface should override
 * <tt>Object.clone</tt> (which is protected) with a public method.
 * See {@link Object#clone()} for details on overriding this
 * method.
 * 按照惯例,实现此接口的类应使用公共方法覆盖Object.clone (受保护)。 有关覆盖此方法的详细信息,请参阅Object.clone() 。
 * <p>
 * Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
 * Therefore, it is not possible to clone an object merely by virtue of the
 * fact that it implements this interface.  Even if the clone method is invoked
 * reflectively, there is no guarantee that it will succeed.
 * 注意,此接口不包含clone方法。 因此,只能通过实现该接口的事实来克隆对象是不可能的。
 * 即使克隆方法被反射地调用,也不能保证它成功。
 *
 * @author  unascribed
 * @see     CloneNotSupportedException
 * @see     Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable {
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值