clone()
方法来完成克隆,原型实例需要实现Cloneable接口,并重写clone()
方法ocp原则
在以下情况下,我们就不能根据new 类名()
来生成实例,而是克隆现有实例来生成新实例
new
来创建实例(有的实例非常复杂,想要创建一个一模一样的示例非常困难)现在有一只羊叫做tom,其属性为姓名: tom,年龄: 1,颜色:白色
,请编写程序创建和 tom 属性完全相同
的10只羊。
package com.atguigu.prototype;
public class Sheep {
private String name;
private int age;
private String color;
public Sheep(String name, int age, String color) {
super();
this.name = name;
this.age = age;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep [name=" + name + ", age=" + age + ", color=" + color + "]";
}
}
package com.atguigu.prototype;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
//传统的方法
Sheep sheep = new Sheep("tom", 1, "白色");
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep5 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
//....
System.out.println(sheep);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
System.out.println(sheep5);
//...
}
}
【优点】
【缺点】
【改进】
Obiect
类是所有类的根类,Object
类提供了一个clone()
方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable
,该接口表示该类能够复制且具有复制的能力package com.atguigu.prototype.improve;
/**
* 继承Cloneable
*/
public class Sheep implements Cloneable {
private String name;
private int age;
private String color;
private String address = "蒙古羊";
/**
* 是对象, 克隆是会如何处理
*/
public Sheep friend;
public Sheep(String name, int age, String color) {
super();
this.name = name;
this.age = age;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep [name=" + name + ", age=" + age + ", color=" + color + ", address=" + address + "]";
}
/**
* 克隆该实例,使用默认的clone方法来完成
*
* @return
*/
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
package com.atguigu.prototype.improve;
public class Client {
public static void main(String[] args) {
System.out.println("原型模式完成对象的创建");
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.friend = new Sheep("jack", 2, "黑色");
Sheep sheep2 = (Sheep)sheep.clone();
Sheep sheep3 = (Sheep)sheep.clone();
Sheep sheep4 = (Sheep)sheep.clone();
Sheep sheep5 = (Sheep)sheep.clone();
System.out.println("sheep2 =" + sheep2 + "sheep2.friend=" + sheep2.friend.hashCode());
System.out.println("sheep3 =" + sheep3 + "sheep3.friend=" + sheep3.friend.hashCode());
System.out.println("sheep4 =" + sheep4 + "sheep4.friend=" + sheep4.friend.hashCode());
System.out.println("sheep5 =" + sheep5 + "sheep5.friend=" + sheep5.friend.hashCode());
}
}
原型模式完成对象的创建
sheep2 =Sheep [name=tom, age=1, color=白色, address=蒙古羊]sheep2.friend=1554874502
sheep3 =Sheep [name=tom, age=1, color=白色, address=蒙古羊]sheep3.friend=1554874502
sheep4 =Sheep [name=tom, age=1, color=白色, address=蒙古羊]sheep4.friend=1554874502
sheep5 =Sheep [name=tom, age=1, color=白色, address=蒙古羊]sheep5.friend=1554874502
Process finished with exit code 0
【分析】
其他sheep的friend
和原型对象的friend
是同一个对象(因为hashcode一样),原因:上面的代码是浅拷贝
,浅拷贝并不会重新复制一个引用类型的对象出来,只是单纯将克隆对象的friend指向原型对象的friend对象,这样的坏处是,如果原型对象的friend发生了改变,克隆对象的friend也会改变,这样不能算是真正的克隆package com.atguigu.prototype.deepclone;
import java.io.Serializable;
public class DeepCloneableTarget implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;
/**
* 构造器
* @param cloneName
* @param cloneClass
*/
public DeepCloneableTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
/**
* 因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.atguigu.prototype.deepclone;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class DeepProtoType implements Serializable, Cloneable {
/**
* 基本数据类型
*/
public String name;
/**
* 引用类型
*/
public DeepCloneableTarget deepCloneableTarget;
public DeepProtoType() {
super();
}
/**
* 深拷贝 - 方式 1 使用clone 方法
*
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
//这里完成对基本数据类型(属性)和String的克隆
Object deep = super.clone();
//对引用类型的属性,进行单独处理
DeepProtoType deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deepProtoType;
}
/**
* 深拷贝 - 方式2 通过对象的序列化实现 (推荐)
* @return
*/
public Object deepClone() {
/// 创建流对象
// 字节输出流
ByteArrayOutputStream bos = null;
// 对象输出流
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//当前这个对象以对象流的方式输出
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType) ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
}
【客户端调用】
package com.atguigu.prototype.deepclone;
public class Client {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
DeepProtoType p = new DeepProtoType();
p.name = "宋江";
p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");
//方式1 完成深拷贝
DeepProtoType p2 = (DeepProtoType) p.clone();
System.out.println("p.name=" + p.name + "; p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
System.out.println("p2.name=" + p.name + "; p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
//方式2 完成深拷贝
DeepProtoType p3 = (DeepProtoType) p.deepClone();
System.out.println("p3.name=" + p.name + "; p3.deepCloneableTarget=" + p3.deepCloneableTarget.hashCode());
}
}
【运行】
p.name=宋江; p.deepCloneableTarget=1554874502
p2.name=宋江; p2.deepCloneableTarget=1846274136
p3.name=宋江; p3.deepCloneableTarget=932172204
Process finished with exit code 0
【分析】
可以看到三个对象的deepCloneableTarget的hashCode不同,因此克隆成功
【框架】
package framework;
import java.lang.Cloneable;
public interface Product extends Cloneable {
/**
* 实例使用
* @param s
*/
public abstract void use(String s);
/**
* 实例复制
* @return
*/
public abstract Product createClone();
}
package framework;
import java.util.*;
public class Manager {
/**
* 存储实例名及实例
*/
private HashMap showcase = new HashMap();
/**
* 注册示例
* @param name
* @param proto
*/
public void register(String name, Product proto) {
showcase.put(name, proto);
}
/**
* 根据实例名称使用实例
* @param protoname
* @return
*/
public Product create(String protoname) {
Product p = (Product) showcase.get(protoname);
return p.createClone();
}
}
【实现类】
import framework.*;
/**
* use 方法的作用是将字符串用双引号括起来显示,并在字符串下面加上下划线。例如,当 ulchar 保存的字符为'~' 方法接收到的字符串为Hello时,显示结果如下
*
* "Hellor"
* ~~~~~~
*/
public class UnderlinePen implements Product {
private char ulchar;
public UnderlinePen(char ulchar) {
this.ulchar = ulchar;
}
public void use(String s) {
int length = s.getBytes().length;
System.out.println("\"" + s + "\"");
System.out.print(" ");
for (int i = 0; i < length; i++) {
System.out.print(ulchar);
}
System.out.println("");
}
public Product createClone() {
Product p = null;
try {
p = (Product) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
import framework.*;
/**
* decochar字段中保存的是像装饰方框那样的环绕着字符串的字符。use方法会使用decochar字段中保存的字符把要显示的字符串框起来。
* 例如,当decochar 中保存的字符为'*',use方法接收到的字符串为 Hello 的时候,显示结果如下
*
* *******
* *Hello*
* *******
*/
public class MessageBox implements Product {
private char decochar;
public MessageBox(char decochar) {
this.decochar = decochar;
}
public void use(String s) {
int length = s.getBytes().length;
for (int i = 0; i < length + 4; i++) {
System.out.print(decochar);
}
System.out.println("");
System.out.println(decochar + " " + s + " " + decochar);
for (int i = 0; i < length + 4; i++) {
System.out.print(decochar);
}
System.out.println("");
}
public Product createClone() {
Product p = null;
try {
p = (Product) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
【主类】
import framework.*;
public class Main {
public static void main(String[] args) {
// 准备
Manager manager = new Manager();
UnderlinePen upen = new UnderlinePen('~');
MessageBox mbox = new MessageBox('*');
MessageBox sbox = new MessageBox('/');
manager.register("strong message", upen);
manager.register("warning box", mbox);
manager.register("slash box", sbox);
// 生成
Product p1 = manager.create("strong message");
p1.use("Hello, world.");
System.out.println();
Product p2 = manager.create("warning box");
p2.use("Hello, world.");
System.out.println();
Product p3 = manager.create("slash box");
p3.use("Hello, world.");
}
}
"Hello, world."
~~~~~~~~~~~~~
*****************
* Hello, world. *
*****************
/
/ Hello, world. /
/
Process finished with exit code 0
在示例程序中,一共出现了如下3种样式。
本例比较简单,只生成了3种样式,不过无论多少种样式都可以生成。但是如果将每种样式都编写为一个类,类的数量将会非常庞大,源代码的管理也会变得非常困难。
在示例程序中,我们将复制(clone)实例的部分封装在framework
包中了
在Manager类的create方法中,我们并没有使用类名,而是根据"strong message"和"slash box"等字符串(即实例名称)来生成相应的实例。与Java语言自带的生成实例的newSomething()方式相比,这种方式具有更好的通用性,且将框架从类名的束缚中解脱出来了
Prototype(原型)
:定义用于复制现有实例来生成新实例的方法,如上面的ProductConcretePrototype(具体的原型)
:负责实现复制现有实例并生成新实例的方法,如上面的MessageBox和UnderlinePenClient(使用者)
:负责使用复制实例的方法生成新的实例,如上面的Manager
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<bean id="id01" class="com.atguigu.spring.bean.Monster"
scope="prototype"/>
beans>
scope=“prototype”:使用原型模式
package com.atguigu.spring.bean;
/**
* 注释
* @author Administrator
*
*/
public class Monster {
private Integer id = 10 ;
private String nickname = "牛魔王";
private String skill = "芭蕉扇";
public Monster() {
System.out.println("monster 创建..");
}
public Monster(Integer id, String nickname, String skill) {
//System.out.println("Integer id, String nickname, String skill被调用");
this.id = id;
this.nickname = nickname;
this.skill = skill;
}
public Monster( String nickname, String skill,Integer id) {
this.id = id;
this.nickname = nickname;
this.skill = skill;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
@Override
public String toString() {
return "Monster [id=" + id + ", nickname=" + nickname + ", skill="
+ skill + "]";
}
}
package com.atguigu.spring.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ProtoType {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 获取monster[通过id获取monster]
Object bean = applicationContext.getBean("id01");
System.out.println("bean" + bean); // 输出 "牛魔王" .....
Object bean2 = applicationContext.getBean("id01");
System.out.println("bean2" + bean2); //输出 "牛魔王" .....
System.out.println(bean == bean2); // false,并不是同一个对象,只是属性相同
}
}
【运行】
monster 创建..
beanMonster [id=10, nickname=牛魔王, skill=芭蕉扇]
monster 创建..
bean2Monster [id=10, nickname=牛魔王, skill=芭蕉扇]
false
Process finished with exit code 0
基本数据类型
的成员变量,浅拷贝会直接进行值传递
,也就是将该属性值复制一份给新的对象引用数据类型
的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递
,也就是只是将该成员变量的引用值 (内存地址)复制一份给新的对象
。因为实际上两个对象的该成员变量都指同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象
【实现方式】
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class DeepCloneUtil {
public static Object deepClone(Object srcObject){
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
Object result = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(srcObject);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
result = ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}
使用方式
A a = new A();
A a1 = (A)DeepCloneUtil.deepClone(a);
CloneNotSupportedException异常
标记接口(marker interface)