Design pattern study notes (6) the difference between prototype mode and deep copy

Prototype mode is also a way to create objects. It is generally used in such a scenario: there are a lot of creation problems of the same or similar objects in the system. If you use traditional constructors to create objects, it will be more complicated and resource-intensive. At this time, using the cloning method of the prototype mode can save a lot of time. For example, what is provided in the Java class Object clone()is the application of the prototype pattern.

1. Introduction to Prototype Mode

Prototype Design Pattern refers to using an already created instance as a prototype, and creating a new object that is the same as or similar to the prototype by copying the prototype object. There are cloning methods in the Java language, such as shallow copy and deep copy.

For general object creation, it does not cost too much resources, but for the responsible object, such as the data of the object needs to be obtained through complex calculations (such as sorting, calculating hash value), or it needs to be retrieved from RPC, network, For reading in very slow IO such as databases and file systems, you can use the prototype mode to directly copy from other objects at this time, thereby reducing resource consumption.

Second, the realization of the prototype mode

The implementation of prototype mode in Java is deep copy and shallow copy. Let's talk about the difference between deep copy and shallow copy

2.1 Deep copy and shallow copy

2.1.1 Shallow copy

Shallow copy (Shadow Clone) is to copy the member variable in the prototype object to the value type attribute to the clone object, and copy the reference address of the reference class to the clone object:

The implementation code is as follows:

//实现Cloneable接口
public class ShadowCopy implements Cloneable{

    private String name;

    private int id;

    public String getName() {
        return name;
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public ShadowCopy(String name, int id) {
        this.name = name;
        this.id = id;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
//调用测试
public class PrototypeTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        ShadowCopy shadowCopy = new ShadowCopy("ethan", 01);
        ShadowCopy copy = (ShadowCopy) shadowCopy.clone();
        System.out.println("name:" + copy.getName() + " " + "id:" + copy.getId());
        System.out.println(copy == shadowCopy);
    }
}

The results from the last test copy == shadowCopyshow as false, instructions for a shallow copy. Let's look at deep copy again:

2.1.2 Deep Copy

Deep Clone is to copy all objects in the prototype object, regardless of value type or reference type, to the copy object:

So how to implement deep copy? ObjectAnd we found out earlier, why do we need to rewrite  the method when copying  clone? Let's take a look at its source code first, and find that clonethe method is a local method:

/**
     * 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;

fold

As you can see from the comments, for all objects:

  1. x.clone()!=xShould be returned  true, because the clone object cannot be the same object as the original object
  2. x.clone().getClass()==x.getClass()Should be returned  truebecause the cloned object is of the same type as the original object
  3. x.clone().equals(x)should be returned true, since equalsits values ​​are all the same when compared using the method

There are two main steps to implement copying in Java: one is to implement  Cloneablean empty interface, and the other is to call the clone method of the parent class after rewriting the method Object, so why do you do this?Clonesuper.clone()

The copy function is not a commonly used function, so it can be implemented when the object needs it, which is more reasonable, and a class can also implement multiple interfaces in the Java language. For the calling clonemethod, because of the particularity of the semantics of the method, there must be direct support from the JVM, and clonethe method is the calling interface. Once a class calls this method, the copy function can be realized.

2.1.3 Implementation of deep copy

There are many ways to implement deep copy, generally there are several:

1. All objects implement deep copy

In this way, all reference objects in the class need to be copied, so as to realize the deep copy of the class. The code is as follows:

public class CloneExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建被赋值对象
        Address address = new Address(110, "北京");
        People p1 = new People(1, "Java", address);
        // 克隆 p1 对象
        People p2 = p1.clone();
        // 修改原型对象
        p1.getAddress().setCity("西安");
        // 输出 p1 和 p2 地址信息
        System.out.println("p1:" + p1.getAddress().getCity() +
                " p2:" + p2.getAddress().getCity());
    }
    /**
     * 用户类
     */
    static class People implements Cloneable {
        private Integer id;
        private String name;
        private Address address;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        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;
        }

        public People(Integer id, String name, Address address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }

        /**
         * 重写 clone 方法
         * @throws CloneNotSupportedException
         */
        @Override
        protected People clone() throws CloneNotSupportedException {
            People people = (People) super.clone();
            people.setAddress(this.address.clone()); // 引用类型克隆赋值
            return people;
        }
    }
    /**
     * 地址类
     */
    static class Address implements Cloneable {
        private Integer id;
        private String city;

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

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getCity() {
            return city;
        }

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

        /**
         * 重写 clone 方法
         * @throws CloneNotSupportedException
         */
        @Override
        protected Address clone() throws CloneNotSupportedException {
            return (Address) super.clone();
        }
    }
}

fold

2. Realize deep copy through construction method

If the parameter of the construction method is a basic data type or a string type, you can directly assign a value. If it is an object type, you need to create a new object. The implementation code is as follows:

public class CloneExample2 {
    public static void main(String[] args) {
        Address address = new Address(100, "北京");
        People people1 = new People(1, "ethan", address);
        People people2 = new People(people1.getId(), people1.getName(), new Address(people1.getAddress().getId(), people1.getAddress().getCity()));
        
    }

    static class People {
        private Integer id;
        private String name;
        private Address address;

        public People(Integer id, String name, Address address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        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 {
        private Integer id;
        private String city;

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

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getCity() {
            return city;
        }

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

fold

3. Realize deep copy through byte stream

The deep copy can be realized through the byte stream that comes with JDK. It is to first write the prototype object into the byte stream in memory, and then read the information just stored from this byte stream as a new Object returns, then the cloned object and the prototype object do not share any address, the implementation code is as follows:

public class CloneExample3 {
    public static void main(String[] args) {
        Address address = new Address(100, "北京");
        People people1 = new People(1, "ethan", address);
        
        //字节流拷贝对象
        People people2 = StreamClone.clone(people1);
        
    }
    
    static class StreamClone {
        public static <T extends Serializable> T clone(People obj) {
            T cloneObj = null;
            try {
                //写入字节流
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
                objectOutputStream.writeObject(obj);
                objectOutputStream.close();
                //分配内存,写入原始对象并生成新对象
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
                ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
                //返回生成的新对象
                cloneObj = (T) objectInputStream.readObject();
                objectInputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return cloneObj;
        }
    }
    static class People implements Serializable {
        private Integer id;
        private String name;
        private Address address;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        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;
        }

        public People(Integer id, String name, Address address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }
    }

    static class Address implements Serializable {
        private Integer id;
        private String city;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getCity() {
            return city;
        }

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

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

fold

When using byte streams for copying, it should be noted that each object must implement  Serizlizablean interface to identify itself as serializable, otherwise an ( java.io.NotSerizlizableException) exception will be thrown.

4. By Apache Commons Langimplementing deep copy

Compared with method 3, this method can be called directly, and the implementation code is as follows:

People people2 = (People)SerizlizationUtils.clone(people1);
//其他部分和方法3相同,省略

5. Realize deep copy through JSONtool class

Gson gson = new Gson();
People people2 = gson.fromJson(gson.toJson(people1), People.class);

In this approach, identity serialization of Peopleand classes is not required. AddressUsing the JSON tool class will first convert the object into a string, and then convert the string into a new object, so it will not be associated with the prototype object. In this way, deep copy is realized, and the same is true for the implementation of other similar JSON tool classes.

3. Summary

There are two main implementations of the prototype mode in Java: deep copy and shallow copy. The difference between the two is that deep copy will copy the reference object, and shallow copy will only copy the address of the reference object. Deep copying is more time-consuming and resource-intensive than shallow copying.

Why is there a deep copy? Because for mutable objects, shallow copying will bring modification risks to the address copy of the referenced object. Therefore, in the scene of mutable objects, try to choose the deep copy method for copying.

Guess you like

Origin blog.csdn.net/weixin_45536242/article/details/125766842