前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >工作三年,小胖连 Clone 源码都没读过?真的菜!

工作三年,小胖连 Clone 源码都没读过?真的菜!

作者头像
JavaFish
发布2021-02-03 16:34:34
3470
发布2021-02-03 16:34:34
举报

哈喽,我是狗哥。这是 Java 源码剖析的第三篇。克隆这个知识点在工作中使用不多,很容易被人忽略。但是面试中的面试官就很常问,因此小伙伴们还是要了解下。另外前两篇的链接在这里有兴趣的小伙伴可以看看:

1、工作三年,小胖连 String 源码都没读过?真的菜!

2、工作三年,小胖连 HashMap 源码都没读过?真的菜!

1. 什么是浅克隆和深克隆?

❝克隆是指生物体通过体细胞进行的无性繁殖,以及由无性繁殖形成的基因型完全相同的后代个体。中学生物课本上的克隆羊多莉就属于这类生物。出处:百度百科 ❞

而在 Java 领域,克隆 Java 基础的一部分,它是指快速地构建出一个已有对象的副本。

「浅克隆(Shadow Clone)「是把原型对象中」成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象」,也就是「原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的」

说句人话就是,浅克隆只会复制原型对象,但不会复制它所引用的对象,我了方便理解,我画张图:

Java 浅克隆

「深克隆(Deep Clone)「是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说」深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象」,画张图理解:

Java 深克隆

2. 如何实现克隆?

在 Java 中实现克隆,「首先要需要实现 Cloneable 接口、其次重写 Object 类的 clone () 方法」,代码如下:

代码语言:javascript
复制
**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.clone <br/>
 * Date:2021/1/31 20:10 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class CloneExample {

    static class User implements Cloneable {

        // 年龄
        private Integer age;

        // 名称
        private String name;

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {

        // 创建对象
        User userOne = new User();
        userOne.setAge(22);
        userOne.setName("clone");

        // 克隆 userOne
        User userTwo = (User) userOne.clone();

        // 打印
        System.out.println("userTwo: " + userTwo.getName());
    }
}

运行结果:

代码语言:javascript
复制
userTwo: clone

3. 克隆有什么规则?

要了解克隆的规则,就必须要从读源码开始,贴出源码:

代码语言:javascript
复制
/**
     * Creates and returns a copy of this object.  The precise meaning
     * of "copy" may depend on the class of the object. The general
     * intent is that, for any object {@code x}, the expression:
     * <blockquote>
     * <pre>
     * x.clone() != x</pre></blockquote>
     * will be true, and that the expression:
     * <blockquote>
     * <pre>
     * x.clone().getClass() == x.getClass()</pre></blockquote>
     * will be {@code true}, but these are not absolute requirements.
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be {@code true}, this is not an absolute requirement.
     * <p>
     * By convention, the returned object should be obtained by calling
     * {@code super.clone}.  If a class and all of its superclasses (except
     * {@code Object}) obey this convention, it will be the case that
     * {@code x.clone().getClass() == x.getClass()}.
     * <p>
     * By convention, the object returned by this method should be independent
     * of this object (which is being cloned).  To achieve this independence,
     * it may be necessary to modify one or more fields of the object returned
     * by {@code super.clone} before returning it.  Typically, this means
     * copying any mutable objects that comprise the internal "deep structure"
     * of the object being cloned and replacing the references to these
     * objects with references to the copies.  If a class contains only
     * primitive fields or references to immutable objects, then it is usually
     * the case that no fields in the object returned by {@code super.clone}
     * need to be modified.
     * <p>
     * The method {@code clone} for class {@code Object} performs a
     * specific cloning operation. First, if the class of this object does
     * not implement the interface {@code Cloneable}, then a
     * {@code CloneNotSupportedException} is thrown. Note that all arrays
     * are considered to implement the interface {@code Cloneable} and that
     * the return type of the {@code clone} method of an array type {@code T[]}
     * is {@code T[]} where T is any reference or primitive type.
     * Otherwise, this method creates a new instance of the class of this
     * object and initializes all its fields with exactly the contents of
     * the corresponding fields of this object, as if by assignment; the
     * contents of the fields are not themselves cloned. Thus, this method
     * performs a "shallow copy" of this object, not a "deep copy" operation.
     * <p>
     * The class {@code Object} does not itself implement the interface
     * {@code Cloneable}, so calling the {@code clone} method on an object
     * whose class is {@code Object} will result in throwing an
     * exception at run time.
     *
     * @return     a clone of this instance.
     * @throws  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable
     */
    protected native Object clone() throws CloneNotSupportedException;

因为是 native 方法,所以看代码没啥好看的。主要看注释,clone () 方法的规则主要有三条:

  • 对于所有对象,x.clone () !=x 应当返回 true,因为克隆对象与原对象不是同一个对象。
  • 对于所有对象,x.clone ().getClass () == x.getClass () 应当返回 true,因为克隆对象与原对象的类型是一样的。
  • 对于所有对象,x.clone ().equals (x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。

4. Arrays.copyOf () 是深克隆还是浅克隆?

对于数组类型的克隆,我们一般使用 Arrays.copy () 实现,代码如下:

代码语言:javascript
复制
// 原型对象
User[] userArrayOne = {new User(22, "Java")};
// 克隆对象
User[] userArrayTwo = Arrays.copyOf(userArrayOne, userArrayOne.length);

// 修改克隆对象的第一个元素的值
userArrayTwo[0].setAge(25);

// 打印
System.out.println("userOne age : " + userArrayOne[0].getAge());
System.out.println("userTwo age : " + userArrayTwo[0].getAge());

运行结果:

代码语言:javascript
复制
userOne age : 25
userTwo age : 25

注意这段代码的第七句,我改变了克隆对象的 age 属性值。紧接着打印输出二者的 age 属性值。发现两个属性值是一样的。为什么呢?

深浅克隆的区别是浅克隆只会复制原型对象,并不会复制它所引用的对象。而深克隆会把引用的对象也给复制了。而在这里,「数组是原型对象,它所引用的对象是里面的 User。」

「我改变了克隆对象其中的 User 的 age,紧接着发现原型对象中的 User 的 age 属性也改变了。这就说明了,Arrays.copy () 是浅克隆,两个对象数组中的 User 只是地址值,它两都指向同一个 User 引用对象」

如果是深克隆,不管是修改原型对象还是引克隆对象中的 User 属性值,另一个应当是不变的。

PS:反之,修改原型对象中的 User 的属性值,克隆对象中对应引用对象的属性值也会改变。

5. 深克隆还有哪些实现方式?

深克隆的实现方式很多,总的来说有以下几种:

  • 所有对象都实现克隆方法。
  • 通过构造方法实现深克隆。
  • 使用 JDK 自带的字节流。
  • 使用第三方工具实现,比如:Apache Commons Lang。
  • 使用 JSON 工具类实现,比如:Gson,FastJSON 等等。
1、有对象都实现克隆方法
代码语言:javascript
复制
/**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.clone <br/>
 * Date:2021/1/31 20:57 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class DeepCloneOneExample {

    static class User implements Cloneable {

        public User() {
        }

        public User(Integer age, String name, Address address) {
            this.age = age;
            this.name = name;
            this.address = address;
        }

        // 年龄
        private Integer age;

        // 名称
        private String name;

        // 地址
        private Address address;

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            User user = (User) super.clone();
            // 引用类型克隆赋值
            user.setAddress((Address) this.address.clone());
            return user;
        }
    }


    static class Address implements Cloneable {

        public Address() {
        }

        public Address(String province, String city) {
            this.province = province;
            this.city = city;
        }

        // 省份
        private String province;

        // 城市
        private String city;

        public String getProvince() {
            return province;
        }

        public void setProvince(String province) {
            this.province = province;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return (Address) super.clone();
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建被赋值对象
        Address address = new Address("广东", "广州");
        User userOne = new User(22, "clone", address);

        // 克隆 userOne 对象
        User userTwo = (User) userOne.clone();

        // 修改原型对象
        userOne.getAddress().setCity("清远");

        // 输出 p1 和 p2 地址信息
        System.out.println("userOne:" + userOne.getAddress().getCity() +
                ",userTwo:" + userTwo.getAddress().getCity());
    }
}

运行结果:

代码语言:javascript
复制
userOne:清远,userTwo:广州

可以看到,修改了原型对象的引用对象并没有改变克隆对象的引用对象。说明两者引用对象已经不是同一个引用对象了,所以是深克隆。

2、通过构造方法实现深克隆

《Effective Java》 中「推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象」,实现代码如下:

代码语言:javascript
复制
/**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.clone <br/>
 * Date:2021/1/31 21:16 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class DeepCloneSecondExample {

    static class User {

        public User() {
        }

        public User(Integer age, String name, Address address) {
            this.age = age;
            this.name = name;
            this.address = address;
        }

        // 年龄
        private Integer age;

        // 名称
        private String name;

        // 地址
        private Address address;

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }
    }


    static class Address {

        public Address() {
        }

        public Address(String province, String city) {
            this.province = province;
            this.city = city;
        }

        // 省份
        private String province;

        // 城市
        private String city;

        public String getProvince() {
            return province;
        }

        public void setProvince(String province) {
            this.province = province;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建对象
        Address address = new Address("广东", "广州");
        User userOne = new User(22, "clone", address);

        // 调用构造函数克隆对象
        User userTwo = new User(userOne.getAge(), userOne.getName(),
                new Address(userOne.getAddress().getProvince(), userOne.getAddress().getCity()));

        // 修改原型对象
        userOne.getAddress().setCity("清远");

        // 输出 userOne 和 userOne 地址信息
        System.out.println("userOne:" + userOne.getAddress().getCity() +
                ",userTwo:" + userTwo.getAddress().getCity());
    }
}

运行结果:

代码语言:javascript
复制
userOne:清远,userTwo:广州
3、使用 JDK 自带的字节流

使用 JDK 自带字节流实现,先将要原型对象写入到内存中的字节流,再从这个字节流中读出刚刚存储的信息,作为一个新对象返回,此时这个新对象和原型对象就不存在任何地址上的共享,从而实现深克隆,代码如下:

代码语言:javascript
复制
/**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.clone <br/>
 * Date:2021/1/31 21:25 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class DeepCloneThirdExample {

    static class User implements Serializable{

        public User() {
        }

        public User(Integer age, String name, Address address) {
            this.age = age;
            this.name = name;
            this.address = address;
        }

        // 年龄
        private Integer age;

        // 名称
        private String name;

        // 地址
        private Address address;

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }
    }


    static class Address implements Serializable {

        public Address() {
        }

        public Address(String province, String city) {
            this.province = province;
            this.city = city;
        }

        // 省份
        private String province;

        // 城市
        private String city;

        public String getProvince() {
            return province;
        }

        public void setProvince(String province) {
            this.province = province;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }
    }


    /**
     * 通过字节流实现克隆
     */
    static class StreamClone {
        public static <T extends Serializable> T clone(User user) {
            T cloneObj = null;
            try {
                // 写入字节流
                ByteArrayOutputStream bo = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(bo);
                oos.writeObject(user);
                oos.close();
                // 分配内存,写入原始对象,生成新对象
                // 获取上面的输出字节流
                ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
                ObjectInputStream oi = new ObjectInputStream(bi);
                // 返回生成的新对象
                cloneObj = (T) oi.readObject();
                oi.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return cloneObj;
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建对象
        Address address = new Address("广东", "广州");
        User userOne = new User(22, "clone", address);

        // 调用构造函数克隆对象
        User userTwo = StreamClone.clone(userOne);

        // 修改原型对象
        userOne.getAddress().setCity("清远");

        // 输出 userOne 和 userOne 地址信息
        System.out.println("userOne:" + userOne.getAddress().getCity() +
                ",userTwo:" + userTwo.getAddress().getCity());
    }

}

运行结果:

代码语言:javascript
复制
userOne:清远,userTwo:广州

结果还是一样的,但这里要注意下。由于是通过字节流序列化实现的深克隆,所以每个对象必须能被序列化。也即必须实现 Serializable 接口。

4、通过第三方工具实现深克隆

比如:Apache Commons Lang,实现代码如下:

代码语言:javascript
复制
/**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.clone <br/>
 * Date:2021/1/31 21:41 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class DeepCloneFourthExample {

    static class User implements Serializable {

        public User() {
        }

        public User(Integer age, String name, Address address) {
            this.age = age;
            this.name = name;
            this.address = address;
        }

        // 年龄
        private Integer age;

        // 名称
        private String name;

        // 地址
        private Address address;

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }
    }
    
    static class Address implements Serializable {

        public Address() {
        }

        public Address(String province, String city) {
            this.province = province;
            this.city = city;
        }

        // 省份
        private String province;

        // 城市
        private String city;

        public String getProvince() {
            return province;
        }

        public void setProvince(String province) {
            this.province = province;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }
    }

    public static void main(String[] args) {
        // 创建对象
        Address address = new Address("广东", "广州");
        User userOne = new User(22, "clone", address);

        // 调用 apache.commons.lang 克隆对象
        User userTwo = SerializationUtils.clone(userOne);

        // 修改原型对象
        userOne.getAddress().setCity("清远");

        // 输出 userOne 和 userOne 地址信息
        System.out.println("userOne:" + userOne.getAddress().getCity() +
                ",userTwo:" + userTwo.getAddress().getCity());
    }

}

输出结果:

代码语言:javascript
复制
userOne:清远,userTwo:广州

实际上,这种工作中我自己是用的比较多的一种方法。因为方便,它跟第三种有点像。其实底层是一样的,还是李用字节流实现。

5、使用 JSON 工具类实现

比如 Gson 或者 FastJson 等等,下面以 Gson 为例,实现代码如下:

代码语言:javascript
复制
/**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.clone <br/>
 * Date:2021/1/31 21:58 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class DeepCloneFifthExample {

    static class User {

        public User() {
        }

        public User(Integer age, String name, Address address) {
            this.age = age;
            this.name = name;
            this.address = address;
        }

        // 年龄
        private Integer age;

        // 名称
        private String name;

        // 地址
        private Address address;

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }
    }

    static class Address {

        public Address() {
        }

        public Address(String province, String city) {
            this.province = province;
            this.city = city;
        }

        // 省份
        private String province;

        // 城市
        private String city;

        public String getProvince() {
            return province;
        }

        public void setProvince(String province) {
            this.province = province;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }
    }

    public static void main(String[] args) {
        // 创建对象
        Address address = new Address("广东", "广州");
        User userOne = new User(22, "clone", address);

        // 调用 Gson 克隆对象
        Gson gson = new Gson();
        User userTwo = gson.fromJson(gson.toJson(userOne), User.class);

        // 修改原型对象
        userOne.getAddress().setCity("清远");

        // 输出 userOne 和 userTwo 地址信息
        System.out.println("userOne:" + userOne.getAddress().getCity() +
                ",userTwo:" + userTwo.getAddress().getCity());
    }
}

运行结果:

代码语言:javascript
复制
userOne:清远,userTwo:广州

这种方法会先把对象转化成字符串,再从字符串转化成新的对象,因为新对象是从字符串转化而来的,因此不会和原型对象有任何的关联,所以实现了深克隆。

6. 总结

本文介绍了深浅克隆的概念、区别;怎么实现客隆;克隆有啥约定俗成的规则;Arrays.copy () 是深克隆还是浅克隆;以及深刻龙的几种实现方式。希望对你有帮助~

7. 巨人的肩膀

  • https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/detail/pc?id=1767

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个优秀的废人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 什么是浅克隆和深克隆?
  • 2. 如何实现克隆?
  • 3. 克隆有什么规则?
  • 4. Arrays.copyOf () 是深克隆还是浅克隆?
  • 5. 深克隆还有哪些实现方式?
    • 1、有对象都实现克隆方法
      • 2、通过构造方法实现深克隆
        • 3、使用 JDK 自带的字节流
          • 4、通过第三方工具实现深克隆
            • 5、使用 JSON 工具类实现
            • 6. 总结
            • 7. 巨人的肩膀
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档