博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
clone详解
阅读量:4180 次
发布时间:2019-05-26

本文共 6976 字,大约阅读时间需要 23 分钟。

想要复制一个对象的所有属性,简单的方式就是通过Cloneable接口,你还可以使用工具类BeanUtils的copyProperties方法,还可以使用拷贝工厂或者使用序列化。这篇文章主要介绍怎么使用Cloneable来完成对象的拷贝。

Cloneable接口的作用是表明这个对象允许克隆。如果一个类实现了Cloneable,那么Object的clone方法就返回该对象的逐域拷贝;没有实现Cloneable,调用Object的clone方法将会抛出CloneNotSupportedException异常。

一个简单的例子

@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class People implements Cloneable {
private Long id; private String code; private String name; private Integer age; @Override public People clone() { People people; try { people = (People) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new AssertionError();//can't happen } return people; } public static void main(String[] args) { People.PeopleBuilder builder = People.builder(); People people = builder.id(12L) .name("mbtlami") .code("2018") .age(100) .build(); People clone = people.clone();//it's the clone object System.out.println(people); }}

但是如果对象的域中包含有可变的对象,使用以上方式只能简单的将可变对象的引用地址复制给克隆出来的对象。这样不管是原对象还是克隆对象,有一方修改了这个可变对象内的属性值,另一方同样会体现出来,这就导致了严重的后果。

在Person类中新增加一个引用对象“宠物”,修改people的宠物名称为cat,clone出来的对象的宠物名称也变为了cat:

private Pet pet;@Datapublic class Pet{    private String name;}public static void main(String[] args) {    Pet dog = new Pet("dog");    People.PeopleBuilder builder = People.builder();    People people = builder.id(12L)            .name("mbtlami")            .code("2018")            .age(100)            .pet(dog)            .build();    People clone = people.clone();//it's the clone object    System.out.println(people);    people.getPet().setName("cat");//modify Pet name    System.out.println(clone);//print cat}

如何保证clone方法正常工作呢?必须进行深克隆。有一个需要注意的地方:clone和引用可变对象的final域是不兼容的,给Pet属性增加final修饰符将不能正常工作。

浅克隆(ShallowClone)和深克隆(DeepClone)都是克隆对象,他们的主要区别在于是否支持引用类型的成员变量的复制。那么怎么进行深克隆呢?让Pet实现Cloneable并重写clone方法,还需要修改People的clone方法。

@Datapublic class Pet implements Cloneable {
private String name; public Pet(String name) { this.name = name; } @Override public Pet clone() { try { return (Pet) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new AssertionError(); } }}class People {... @Override public People clone() { People people; try { people = (People) super.clone(); if (pet != null) { people.pet = pet.clone(); } } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new AssertionError();//can't happen } return people; }}people.getPet().setName("cat");System.out.println(clone);//People{id=12, code='2018', name='mbtlami', age=25, isMarried=null, pet=Pet(name=dog)}

如果People内定义了一个散列桶数组,实现了轻量级的单向链表,仅仅只是递归克隆对象,虽然被克隆的对象有它自己的散列桶,但是数组引用的链表还是和原对象一样的,这样也会导致问题。

class Person{...    private Node
[] table; static class Node
{ final int hash; final K key; V value; Node
next; Node(int hash, K key, V value, Node
next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } ... } @Override public People clone() { People people; try { people = (People) super.clone(); if (pet != null) { people.pet = pet.clone(); } if (table != null) { people.table = table.clone(); } } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new AssertionError();//can't happen } return people; }}
People.Node
[] nodes = new People.Node[10];People.Node
node12 = new People.Node<>("mbtlami12".hashCode(), "mbtlami12", "mbtlami12", null);People.Node
node11 = new People.Node<>("mbtlami11".hashCode(), "mbtlami11", "mbtlami11", node12);nodes[0] = new People.Node("mbtlami1".hashCode(), "mbtlami1", "mbtlami1", node11);People.PeopleBuilder builder = People.builder();People people = builder.id(12L) .name("mbtlami") .code("2018") .pet(dog) .age(25) .table(nodes) .build();People clone = people.clone();People.Node
stringObjectNode = clone.getTable()[0];System.out.println(stringObjectNode);People.Node
[] table = people.getTable();table[0].next = null;System.out.println(stringObjectNode);//Node{hash=1192532577, key=mbtlami1, value=mbtlami1, next=Node{hash=-1686195728, key=mbtlami11, value=mbtlami11, next=Node{hash=-1686195727, key=mbtlami11, value=mbtlami11, next=null}}}//Node{hash=1192532577, key=mbtlami1, value=mbtlami1, next=null}

深克隆也需要注意拷贝每一个散列桶的非空链表,在Node中定义deepClone方法:

Node
deepClone() { return new Node
(hash, key, value, next == null ? null : next.deepClone());}

这样就没问题了吗?请注意递归克隆在链表比较长的话,很容易导致栈溢出,可以使用迭代遍历代替递归。

Node
deepCopy() { Node
node = new Node
(hash, key, value, next); for (Node p = node;p.next!=null;p = p.next) { p.next = new Node(p.next.hash, p.next.key, p.next.value, p.next.next); } return node;}

这样相对完整的People对象的clone方法是这样的:

@Override    public People clone() {        People people;        try {            people = (People) super.clone();            if (pet != null) {                people.pet = pet.clone();            }            if (table != null) {                /*people.table = table.clone();*/                people.table = new Node[table.length];                for (int i = 0; i < people.table.length; i++) {                    if (table[i] != null) {                        /*people.table[i] = table[i].deepClone();*/                        people.table[i] = table[i].deepCopy();                    }                }            }        } catch (CloneNotSupportedException e) {            e.printStackTrace();            throw new AssertionError();//can't happen        }        return people;    }

完整的例子请查看。

此外,还有需要注意的地方,如果是为了继承而设计的类需要覆盖clone方法,clone方法需要和Object的clone行为保持一致:声明为protected,抛出异常,并且该父类不应该实现Cloneable接口,这样子类就可以自主选者是否实现Cloneable接口。如果类为线程安全的也需要注意clone方法的同步实现。

序列化方式:People、Pet、Person.Node需要实现Serializable接口。序列化对象也有很多需要注意的地方,详细请查看我的后续文章,这里只给出一个简单的例子。

public static Object cloneObject(Object obj) throws IOException, ClassNotFoundException{    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();    ObjectOutputStream out = new ObjectOutputStream(byteOut);    out.writeObject(obj);    ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());    ObjectInputStream in =new ObjectInputStream(byteIn);    return in.readObject();}

总结:所有实现Cloneable接口的类都应该用一个公有的方法覆盖clone,此方法首先应该调用supper.clone(),然后依次copy引用类型的域,并返回一个自身类型的对象而不是Object类型(方便调用,不由客户端来强制类型转换),并忽略受检异常。

转载地址:http://omrai.baihongyu.com/

你可能感兴趣的文章
进程间的通信---UNIX高级环境编程
查看>>
基于SSH开发的城市公交管理系统 JAVA MySQL
查看>>
基于SSH开发的勤工助学管理系统 JAVA MySQL
查看>>
基于SSH开发的宠物销售商城系统 JAVA MySQL
查看>>
基于springboot的宠物领养管理系统 java
查看>>
JAVA 洗衣房管理系统 宿舍洗衣机管理系统
查看>>
基于SSM的街道办安全管理系统 JAVA
查看>>
基于SSM的论文选题管理系统 JAVA
查看>>
生成器模式
查看>>
工厂方法模式
查看>>
阿里规范(一)关于CountDownLatch和ThreadLocalRandom的详解(带测试代码)
查看>>
Mysql 函数 STR_TO_DATE
查看>>
Commons CLI 使用介绍
查看>>
Mybatis 缓存实现原理——案例实践
查看>>
Mybatis 缓存实现原理
查看>>
怎么提升SQL查询效率
查看>>
预编译防止sql注入
查看>>
覆盖equals方法时总是要覆盖hashCode
查看>>
clone详解
查看>>
【Java并发编程实战】——AbstractQueuedSynchronizer源码分析(一)
查看>>