你需要了解的创建型模式之原型模式

初识原型模式

为什么用

  1. 类初始化消耗太多资源(数据、硬件),为了避免消耗
    • 复杂对象
  2. 给其他对象访问,而且调用者可能需要修改值
    • 访问复杂

生活示例

以文档的拷贝为例,文档中含有文本和图片。为了安全,我们需要将原来的文档拷贝一份副本,在副本上进行修改。

用法

  1. 实现clone方法
  2. 类实现接口Cloneable

Cloneable接口只是标记接口(空实现),如果实现了clone方法,没有实现接口会出异常

Object中的clone方法

protected Object clone() throws CloneNotSupportedException {
        if (!(this instanceof Cloneable)) {
            throw new CloneNotSupportedException("Class " + getClass().getName() +
                                                 " doesn't implement Cloneable");
        }

        return internalClone();
    }

方法实现

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

举个例子(浅拷贝)

这是浅拷贝

public class User implements Cloneable {

    private String name = "1";
    private int age = 2;
  
    public User(){
        System.out.println("----Constructor method----");
    }
    @Override
    protected User clone() {
        try {
            User user = (User) super.clone();
            user.name = this.name;
            user.age = this.age;
            return user;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }


    public void setUser(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toStringUser() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age;
    }
}

输出一下

  public static void main(String[] args) {
        User user = new User();
        //1
        System.out.println(user.toStringUser());
        
        User u2 = user.clone();
        u2.setUser("123", 12231);
        //1
        System.out.println(u2.toStringUser());
        //2
        System.out.println(user.toStringUser());
    }

结果

----Constructor method----
User{name='1', age=2}
User{name='123', age=12231}
User{name='1', age=2}

总结一下浅拷贝

  1. 类变量都是基本数据类型
  2. clone方法生成的对象没有调用构造方法
    • 不调用构造方法会导致某些初始化无法使用
  3. 构造方法产生的对象改变值,并不改变原对象值

举个例子(错误例子)

public class User implements Cloneable {

    private String name = "1";
    private int age = 2;
    private ArrayList<Integer> mList=new ArrayList<>();

    public User(){
        System.out.println("----Constructor method----");
    }
    @Override
    protected User clone() {
        try {
            User user = (User) super.clone();
            user.name = this.name;
            user.age = this.age;
            user.mList = this.mList;
            return user;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }


    public void setUser(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void addList(int i) {
        this.mList.add(i);
    }

    public String toStringUser() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", mList=" + mList +
                '}';
    }
}

输出一下

 public static void main(String[] args) {
        User user = new User();
        user.addList(2);
        System.out.println(user.toStringUser());
        User u2 = user.clone();
        u2.setUser("123", 12231);
        u2.addList(3);
        System.out.println(u2.toStringUser());
        System.out.println(user.toStringUser());
    }

结果

----Constructor method----
User{name='1', age=2, mList=[2]}
User{name='123', age=12231, mList=[2, 3]}
User{name='1', age=2, mList=[2, 3]}

发现一下原因

经过上面的分析浅拷贝,会发现,List是引用类型,那我们大体上也可以了解clone实现的原理了,官方说是这样实现的

在内存中二进制流的拷贝

我们最直观的感受就是拷贝的对象引用了原始对象的字段,因为基本类型存的是值,引用类型存的是地址,和adapter是一样的,我们修改A时B也会改变,因为他们指向同一个地址。

那我们怎么办?为了实现原型模式的作用–深拷贝,我们可以验证一下是否正确

测试一下

public class User implements Cloneable {

    private String name = "1";
    private int age = 2;
    private ArrayList<Integer> mList=new ArrayList<>();

    public User(){
        System.out.println("----Constructor method----");
    }
    @Override
    protected User clone() {
        try {
            User user = (User) super.clone();
            user.name = this.name;
            user.age = this.age;
            user.mList = this.mList;
            return user;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }


    public void setUser(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setList(ArrayList<Integer> mList) {
        this.mList = mList;
    }

    public String toStringUser() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", mList=" + mList +
                '}';
    }
}

输出一下

 public static void main(String[] args) {
        User user = new User();
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        user.setList(list);
        //1
        System.out.println(user.toStringUser());
        
        User u2 = user.clone();
        u2.setUser("123", 12231);
        list=new ArrayList<>();
        list.add(3);
        u2.setList(list);
        
        //2
        System.out.println(u2.toStringUser());
        //1
        System.out.println(user.toStringUser());
    }

结果

----Constructor method----
User{name='1', age=2, mList=[1, 2]}
User{name='123', age=12231, mList=[3]}
User{name='1', age=2, mList=[1, 2]}

结论

以上就是正确的,但是我们不能每次都如此麻烦,深拷贝就是这样么?不是的,我们在初始化时要把引用类型字段也拷贝,而不是单纯的引用。

举例(深拷贝)

public class User implements Cloneable {

    private String name = "1";
    private int age = 2;
    private ArrayList<Integer> mList=new ArrayList<>();

    public User(){
        System.out.println("----Constructor method----");
    }
    @Override
    protected User clone() {
        try {
            User user = (User) super.clone();
            user.name = this.name;
            user.age = this.age;
            user.mList = (ArrayList<Integer>) this.mList.clone();
            return user;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }


    public void setUser(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void addList(int i) {
        this.mList.add(i);
    }

    public String toStringUser() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", mList=" + mList +
                '}';
    }
}

输出一下

   User user = new User();
        user.addList(1);
        user.addList(2);
        System.out.println(user.toStringUser());

        User u2 = user.clone();
        u2.setUser("123", 12231);
        u2.addList(3);
        System.out.println(u2.toStringUser());
        System.out.println(user.toStringUser());

结果

User{name='1', age=2, mList=[1, 2]}
User{name='123', age=12231, mList=[1, 2, 3]}
User{name='1', age=2, mList=[1, 2]}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值