`
qqdwll
  • 浏览: 131230 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Java 中的Clone 学习总结

    博客分类:
  • Java
阅读更多
1. 一个类需要实现clone. 一个最佳实践是它需要实现 Cloneable 接口并且提供一个 public clone 方法。

Object 对象的clone 方法是protected。 不重写这个方法, 我们不能够调用一个对象的clone 方法, 除非利用反射。

2.  如果给一个 nonfinal 类重写clone方法。应该通过调用 super.clone获得对象。
因为有个约束, x.clone().getClass() 应该和x.getClass()一致。 所以, 也不要通过构造器去实现clone.

3。当调用clone方法后, 这个类定义的所有属性都回被copy, 并且clone后所有属性的值都会跟那个源对象一样。 如果这个类的所有属性是基本类型或者引用的是不可改变的类型(final) , 就像 String, 那么, 这个clone后获得的对象, 就是我们确切需要的对象。它不需要进一步做任何操作 (当然, 如果某些值是要做唯一性处理的。 也要改)。

package util;

public final class PhoneNumber {
	private final short areaCode;
	private final short exchange;
	private final short extension;

	public PhoneNumber(int areaCode, int exchange, int extension) {
		rangeCheck(areaCode, 999, "area code");
		rangeCheck(exchange, 999, "exchange");
		rangeCheck(extension, 9999, "extension");
		this.areaCode = (short) areaCode;
		this.exchange = (short) exchange;
		this.extension = (short) extension;
	}

	private static void rangeCheck(int arg, int max, String name) {
		if (arg < 0 || arg > max)
			throw new IllegalArgumentException(name + ": " + arg);
	}

	public boolean equals(Object o) {
		if (o == this)
			return true;
		if (!(o instanceof PhoneNumber))
			return false;
		PhoneNumber pn = (PhoneNumber) o;
		return pn.extension == extension && pn.exchange == exchange
				&& pn.areaCode == areaCode;
	}

	[b]public Object clone() {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			throw new Error("Assertion failure"); // Can't happen
		}
	}[/b]
}



但是, 当有fields引用可变的对象时候, 简单的使用上面的实现, 会发生错误。 例如
public class Stack {
	private Object[] elements;
	private int size = 0;

	public Stack(int initialCapacity) {
		this.elements = new Object[initialCapacity];
	}

	public void push(Object e) {
		ensureCapacity();
		elements[size++] = e;
	}

	public Object pop() {
		if (size == 0)
			throw new EmptyStackException();
		Object result = elements[--size];
		elements[size] = null; // Eliminate obsolete reference
		return result;
	}

	// Ensure space for at least one more element.
	private void ensureCapacity() {
		if (elements.length == size) {
			Object oldElements[] = elements;
			elements = new Object[2 * elements.length + 1];
			System.arraycopy(oldElements, 0, elements, 0, size);
		}
	}
}


如果需要把上面的这个类可以cloneable, 而clone方法, 仅仅返回super.clone(). 对于field size会有正确的值, 但field elements 会引用同一个对象。 改变原对象或克隆对象时候, 都会同时影响两个对象。 这时候, 参考下面的原则。

4. clone 方法是作为另外一个构造方法, 你必须保证克隆对象不会影响到源对象。 这时需要对clone方法做一些改变。即我们需要做深度克隆。 例如:

	public Object clone() throws CloneNotSupportedException {
		Stack result = (Stack) super.clone();
		result.elements = (Object[]) elements.clone();
		return result;
	}


这样的话, 其实要每一层, 每个可变对象都实现良好的克隆。 但是

如果一个类的有些field 是 final的话, 会有些麻烦。 因为克隆中, 试图给一个final field赋值是不可能的(java 基本特性)。 这时候, 可能考虑两种做法。 一是如果可变对象可以在克隆对象和源对象之间安全共享的话, 就不需要做深度克隆 (这里的final, 可变对象不会给大家造成混淆吧?)。另外一种做法是把final 去掉。

从JDK中取个deeply copy 的例子:
HashMap 实现的事shallow copy, HashTable实现的话,某些是deep copy. 例如里面的一个链表实现。
这里简单的介绍下HashTable的实现。
HashTable 里有个Entry 对象数组 Entry[] buckets。
根据上面的分析,我们知道要实现深度克隆, 需要递归调用buckets.clone方法。 如果buckets 数组里的对象仍然还是可变对象 (Entry), 那么还要递归调用Entry.clone方法。 直到基本类型或final类型。  参考下面的源代码片断:

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {

    /**
     * The hash table data.
     */
    private transient Entry[] table;

    /**
     * The total number of entries in the hash table.
     */
    private transient int count;

    /**
     * The table is rehashed when its size exceeds this threshold.  (The
     * value of this field is (int)(capacity * loadFactor).)
     *
     * @serial
     */
    private int threshold;

    /**
     * The load factor for the hashtable.
     *
     * @serial
     */
    private float loadFactor;
 ......

// Entry 其实是一个链表的简单实现
  /**
     * Hashtable collision list.  
     */
    private static class Entry<K,V> implements Map.Entry<K,V> {
	int hash;
	K key;
	V value;
	Entry<K,V> next;

	protected Entry(int hash, K key, V value, Entry<K,V> next) {
	    this.hash = hash;
	    this.key = key;
	    this.value = value;
	    this.next = next;
	}

	protected Object clone() {
	    return new Entry<K,V>(hash, key, value,
				  (next==null ? null : (Entry<K,V>) next.clone()));
	}

 .....

 /**
     * Creates a shallow copy of this hashtable. All the structure of the
     * hashtable itself is copied, but the keys and values are not cloned.
     * This is a relatively expensive operation.
     *
     * @return  a clone of the hashtable
     */
    public synchronized Object clone() {
	try {
	    Hashtable<K,V> t = (Hashtable<K,V>) super.clone();
	    t.table = new Entry[table.length];
	    for (int i = table.length ; i-- > 0 ; ) {
		t.table[i] = (table[i] != null)
		    ? (Entry<K,V>) table[i].clone() : null;
	    }
	    t.keySet = null;
	    t.entrySet = null;
            t.values = null;
	    t.modCount = 0;
	    return t;
	} catch (CloneNotSupportedException e) {
	    // this shouldn't happen, since we are Cloneable
	    throw new InternalError();
	}
    }



其实, 上面的Entry 的copy方法在链表elements比较大时候, 会有点问题。 因为每个element都会copy一个链表出来。 这回可能发生stack overflow. 用下面的方法, 可以防止这种情况发生。

            // Iteratively copy the linked list headed by this Entry
           Entry deepCopy() {
           Entry result = new Entry(key, value, next);
          for (Entry p = result; p.next != null; p = p.next)
                    p.next = new Entry(p.next.key, p.next.value, p.next.next);
          return result;
}


5. Clone 方法, 不要调用非final 方法。 如果调用的方法被override. 子类调用clone时, 子类的值可能被改变。 所有一般调用final或者private方法。






0
8
分享到:
评论

相关推荐

    java之Git总结笔记

    例如我们一个小团队合作开发一个项目,我们可以先建立一个远程仓库,需求分析,搭建大体框架,将项目框架上传至远程仓库,队员可以git clone项目,各自完成自己负责的部分,完善项目等,写好之后还可以比对之前的...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    java 编程入门思考

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    JAVA_高级特性(hashCode,clone,比较器,Class反射,序列化)

    总结非常完全的文档。对Java初学着和进阶学习的学者是一份相当不错的Java学习资料

    Thinking in Java简体中文(全)

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    java联想(中文)

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    Thinking in Java 中文第四版+习题答案

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 实现方案的...

    Think-In-Java:学习Java学习之路

    开发环境 开发工具:IDEA 2019.3.1 JDK版本:JDK 1.8 Maven版本:3.6.3 插件环境 杰森 招摇2 运行方式 提示:如果是fork的朋友,同步代码...Java设计模式学习与总结 LetCode刷题汇总 交流 如果大家有兴趣,欢迎大家一

    Java初学者入门教学

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    JAVA_Thinking in Java

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    JAVA_Thinking in Java(中文版 由yyc,spirit整理).chm

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    Think in Java(中文版)chm格式

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 ...

    Java习惯用法总结

    (Joshua Bloch的《Effective Java》对这个话题给出了更详尽的论述,可以从这本书里学习更多的用法。)  我把本文的所有代码都放在公共场所里。你可以根据自己的喜好去复制和修改任意的代码片段,不需要任何的凭证...

    Thinking in Java(中文版 由yyc,spirit整理).chm

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    JAVA-000

    将Fork后的仓库Clone到本地,然后在本地仓库中对应周的目录下新建或修改自己的代码作业,当周的学习总结写在对应周的README.md文件里。 在本地仓库完成作业后,将其推送到自己的GitHub远程仓库。 最后将远程仓库中...

    ThinkInJava

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    javalruleetcode-learn-note:学习知识总结,面试知识扫描

    clone git@github.com:doocs/advanced-java.git 进入 advanced-java 根目录:cd advanced-java 执行命令,运行本项目:docsify serve 数据结构 树 堆 * 操作系统 高并发架构 缓存 分库分表 读写分离 高并发系统 ...

    thinkinjava

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    www.flydean.com:www.flydean.com的原始代码,全领域覆盖,看得上的小伙伴点个星吧!PDF下载出错的小伙伴可以git clone整个项目即可!

    我就是我啦,请叫我F哥,毕业于清华大学,在互联网和金融行业奋斗多年,别的优点没有,就是善于学习和总结。 希望能够把之前用到的,未来学到的知识和技巧总结起来,分享给大家。 读书破万卷,下笔如有神!关注我...

Global site tag (gtag.js) - Google Analytics