02 原型模式
Java中怎么拷贝一个对象呢?
可以通过调用这个对象类型的构造器构造一个新对象,然后将要拷贝对象的属性设置到新对象里面。
1.Java中也有另一种不通过构造器来拷贝对象的方式,这种方式称为克隆。
2.Java提供了java.lang.Cloneable和java.lang.Object中的clone()方法来支持克隆。
使用条件
是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
写法
如果希望一个类拥有克隆的行为,要做一下几步:
1.该类实现java.lang.Cloneable接口。
2.覆盖Object的clone方法,将访问修饰符改为public,修改返回类型。
3.clone方法内部调用父类的clone方法。
/*
* 1.实现cloneable接口
* */
class Graphy implements Cloneable{
Person writer;
/*2.重写clone方法,并注意将访问修饰符改为public,修改返回类型。*/
@Override
public Graphy clone() throws CloneNotSupportedException {
/*3. clone方法内部调用父类的clone方法。父类执行对对象的字段的克隆操作,强转修改 返回类型*/
return (Graphy)super.clone();
}
public Person getWriter() {
return writer;
}
public void setWriter(Person writer) {
this.writer = writer;
}
@Override
public String toString() {
return "graphy{" +
"writer=" + writer +
'}';
}
}
# 为啥这么写
## 原理剖析
由于这段源码很简单而且注释写得太好了,先直接粘贴出来,免得写得不确切,影响读者阅读。
```java
package java.lang;
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <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.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <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.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}
一个类实现Cloneable接口表示可以有对象的克隆的行为的合法性,如果不实现会抛错CloneNotSupportedException
但这个接口中却没有提供clone方法(Cloneable接口中没有任何方法,看起来更像是一个标记接口。 )
按照注解提示 我们通常的做法是重写Object的clone方法并把其改为public方法。
那么我们来看看Object的clone方法的源码和注释吧
/**
* 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;
创建并返回这个类的克隆,准确的描述是这个类的实例对象的克隆。
流程是 如果这个类的对象已经存在,直接复制一份这个对象,里面的属性原封不动的拷贝过来,这涉及到了地址的变化。
如果一个对象没有实现Cloneable接口,调用它的clone方法会抛出CloneNotSupportedException异常。但数组例外,可以认为数组实现了Cloneable,所以可以直接调用其clone方法获取克隆对象。
深克隆与浅克隆(深拷贝与浅拷贝的区别)
按照Object克隆方法描述中的规范,克隆得到对象应该独立于被克隆对象,从根本上说,它们的内存地址(不严格的说)应该不同。但是如果有这种情况,一个可克隆类中包含其他的引用类型属性,那么克隆后会是什么情况呢?举个例子说明一下:
package xzc._04prototype;
/*
* 总结:
* 1.一个类实现cloneable接口
* 1.1点接口源码进去 发现这是一个 标记型号接口 就是 里面没有任何需要我们实现的接口
* 1.2这是一个标记型接口 用于声明合法性 不写抛错
*
* 2.重写clong方法
* 2.1 点开源码会看到 这是一个 protected native 修饰的方法(意味着更底层是c++写的方法)
* 2.2 提示我们使用clong方法 必须写实现Cloneable接口 可以看到所有数组默认已经实现了Cloneable接口可以不用我们去写
* 2.3 重写Clone方法并在里面用 super.clone()实现,将访问修饰符改成public (非必须)修改返回类型为该类的类型。
*
* 3.深拷贝于浅拷贝
*
* */
/*
* 1.实现cloneable接口
* */
class Graphy implements Cloneable {
Person writer;
/*2.重写clone方法,并注意将访问修饰符改为public,修改返回类型。*/
@Override
public Graphy clone() throws CloneNotSupportedException {
/*3. clone方法内部调用父类的clone方法。父类执行对对象的字段的克隆操作,强转修改返回类型*/
return (Graphy) super.clone();
}
public Person getWriter() {
return writer;
}
public void setWriter(Person writer) {
this.writer = writer;
}
@Override
public String toString() {
return "graphy{" +
"writer=" + writer +
'}';
}
}
public class prototypeMode {
public static void main(String[] args) throws CloneNotSupportedException {
Graphy graphy = new Graphy();
Person person = new Person();
Graphy graphy_clone = graphy.clone();
//graphy和graphy_clone是否指向相同的内存地址
System.out.println("graphy_clone == graphy");
System.out.println(graphy_clone == graphy);
//graphy的writer和graphy_clone的writer是否指向相同的内存地址
System.out.println("graphy_clone.getWriter() == graphy.getWriter()");
System.out.println(graphy_clone.getWriter() == graphy.getWriter());
}
}
class Person {
String name = null;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "person{" +
"name='" + name + '\'' +
'}';
}
}
执行结果
可见clone方法默认的行为就是浅克隆。那么也很容易想到,如果克隆一个对象,并递归的克隆其所有的引用类型属性,这样的方式就是深克隆了。
理解了原理我们做如下修改
@Override
public Graphy clone() throws CloneNotSupportedException {
/*3. clone方法内部调用父类的clone方法。父类执行对对象的字段的克隆操作,强转修改返回类型*/
Graphy graphy= (Graphy) super.clone();
Person person= graphy.getWriter().clone();
graphy.setWriter(person);
return graphy;
}
设置了下一层的递归克隆
对其属性的类实现克隆接口
class Person implements Cloneable{
String name = null;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Person clone() throws CloneNotSupportedException {
/*3. clone方法内部调用父类的clone方法。父类执行对对象的字段的克隆操作,强转修改返回类型*/
return (Person)super.clone();
}
@Override
public String toString() {
return "person{" +
"name='" + name + '\'' +
'}';
}
}