clone()方法的约定
Cloneable接口的目的是作为对象的一个混合接口,表明这样的对象允许克隆(clone),但是这个接口却没有定义clone(),这是它的缺陷:无法约束子类实现clone()方法。Object定义了一个受保护的clone()方法。Cloneable虽然没有定义clone()方法,但是却影响了Object.clone()方法的行为:如果一个类实现了Cloneable,调用Object的clone()就会返回该对象的逐域拷贝,否则抛出CloneNotSupportedException。这真是一种非常规的用法,Cloneable接口没有规定实现类的视图,却改变了父类受保护方法的行为。调用clone()会创建并返回对象的拷贝,看看JDK文档中对clone()方法的约定:
(1)x.clone()
!= x; 克隆对象与原对象不是同一个对象
(2)x.clone().getClass()
== x.getClass(); 克隆的是同一类型的对象
(3)x.clone().equals(x)
== true,如果x.equals()方法定义恰当的话
注意,上面的三条规则要求不是绝对的,一般来说前两条是必需的,第三个也应该尽量遵守。
实现Cloneable接口的类和其所有超类都必需遵守一个复杂、不可实施、且没有文档说明的协议,由此得到一种语言之外的机制:无需调用构造器就可以创建对象。然而,“不调用构造器”的规定有些僵硬,行为良好的clone()方法可以调用构造器创建对象,比如final类,它不会有子类,所以在它的clone()方法中调用构造器创建对象是一种合理的选择。
使用clone()的规则
“如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone()而得到的对象”,这是使用clone()方法的规则,如果不遵守这条规则,在clone()方法中调用了构造器,那么就会得到错误的类。如代码所示:
class A implements Cloneable
{<p style="margin:0in;font-size:10.5pt"><span style="font-family:Calibri" lang="en-US"> //</span><span style="font-family:SimSun" lang="zh-CN">类</span><span style="font-family:Calibri" lang="en-US">A</span><span style="font-family:SimSun" lang="zh-CN">的</span><span style="font-family:Calibri" lang="en-US">clone()</span><span style="font-family:SimSun" lang="zh-CN">直接调用构造器</span></p> public A clone() {
return new A();
}
}
class B extends A
{
public B clone() {
try
{
return (B) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new AssertionError();
}
}
}
类B的clone()方法就不会得到正确的对象,因为super.clone()返回的是使用A的构造器创建的类A的对象。如果类B的clone方法想得到正确的对象,那么A的clone方法应该这样写:public A clone() {
try{
return (A) super.clone();
}catch (CloneNotSupportedException e){
throw new AssertionError();
}
}
由此,我们可以看出调用super.clone()最终会调用Object类的clone方法,前提是子类的所有超类都遵循了上面的规则,否则无法实施。注意,A和B的clone方法的返回值不必是Object,Java1.5引入了协变返回类型作为泛型,覆盖方法的返回值可以是被覆盖方法返回值的子类。
Cloneable实在是一个失败的接口,它并没有指明实现它的类需要承担哪些责任,通常情况下,实现Cloneable的类应当提供一个功能适当的公有的clone()方法。
浅克隆
克隆出来的对象的所有变量含有与原来的对象相同的值,而对其他对象的引用都指向原来的对象。也就是说,浅克隆仅仅克隆所考虑的对象。Object的clone就是"shallow
copy"。如果类的每个域都是基本类型的值,或者是指向不可变对象的引用,那么调用Object.clone()就能得到正确的对象。
/**
*如果每个域都是基本类型,或者指向不可变对象的引用
*那么这个类只需要声明实现Cloneable接口,提供公有的clone()方法
*/
class ShallowCopy implements Cloneable
{
private String name;
private int no;
public ShallowCopy(String name,int no) {
this.name = name;
this.no = no;
}
/*只需调用super.clone()就能得到正确的行为*/
public ShallowCopy clone() {
try
{
return (ShallowCopy)super.clone();
}
catch (CloneNotSupportedException e)
{
throw new AssertionError();
}
}
}
通常情况下,我们已经得到了正确的对象,但是如果类里面包含代表序列号或者唯一ID的域,或者创建时间的域,还需要对这些域进行修正。
深克隆
深克隆把引用域所指向的对象也克隆一遍。考虑下面这样一个类:
class Person
{
private Dog friend;
public Person(Dog dog) {
friend = dog;
}
}
class Dog
{
private String name;
public Dog(String name) {
this.name = name;
}
}
Person类的friend域不是基本类型,而是指向了可变的对象,这个时候如果调用Object.clone()进行浅克隆,那么克隆出来的对象的friend指向的还是原来的dog,就是说:
Person p = new Person(new Dog("金毛"));
p.clone().friend== p.friend;//true
p.clone().friend.name = "狼狗";
p.friend.name.equals("狼狗");//true,改变克隆对象,却同时更改了原对象
实际上,clone方法是另一种构造器:你必须确保不会伤害到原来的对象。为了使Person的clone方法正确工作,也要对friend进行克隆,最简单的做法就是调用friend.clone():
public Person clone() {
try{
Person result = (Person) super.clone();
result.friend = friend.clone();
}
catch (CloneNotSupportedException e){
throw new AssertionError();
}
}
可是,如果friend域是final的,那么上面的clone()也无法正常工作,因为super.clone()时已经给friend赋一次值了,不能再去修正克隆对象的friend域了。这是个根本问题:clone架构与引用可变对象的final域的正常用法是不相兼容的!
抛去final域的问题不谈,递归的调用clone()方法就解决问题了吗?问题在于,深克隆要深入到哪一层,是一个不易确定的问题。考虑下面的类:
import java.util.Arrays;
/**
*内部实现了单向链表
*buckets里的每个元素保存一个单向链表
*
*/
class NMap implements Cloneable
{
private Entry[] buckets;
public NMap(int size) {
buckets = new Entry[size];
for(int i = 0; i < size; i++)
buckets[i] = new Entry("M10","Messi",new Entry("X6", "Xavi", null));
}
public Entry[] getBuckets() {
return buckets;
}
static class Entry
{
final Object key;
Object value;
Entry next;
Entry(Object key,Object value,Entry next) {
this.key = key;
this.value = value;
this.next = next;
}
public void setNext(Entry next) {
this.next = next;
}
public String toString() {
String result = key + ":" + value + " ";
if(next != null)
result += next.toString();
return result;
}
}
public NMap clone(){
try
{
NMap result = (NMap)super.clone();
//数组被视为实现了Cloneable接口
result.buckets = buckets.clone();
return result;
}catch (CloneNotSupportedException e){
throw new AssertionError();
}
}
public static void main(String[] args) {
NMap map = new NMap(5);
System.out.println(Arrays.toString(map.getBuckets()));
NMap clone = map.clone();
Entry entry = new Entry("G4","Guadiorla",new Entry("R9","Ronaldo",null));
for(Entry ent : clone.getBuckets())
ent.setNext(entry);
System.out.println(Arrays.toString(map.getBuckets()));
}
}
运行这段代码之后会发现,虽然克隆对象有自己的数组buckets,但是数组中引用的链表与原始对象是一样的,修改克隆对象数组中的链表,原始对象中数组保存的对象也会随之而修改。解决这种问题的方法是在Entry类中增加一个“深度拷贝(deep
copy)”方法。
public Entry deepEntry() {
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;
}
NMap的clone()方法如下:
public NMap clone(){
try
{
NMap result = (NMap)super.clone();
//数组被视为实现了Cloneable接口
result.buckets = buckets.clone();
for(int i = 0; i < buckets.length; i++)
if(buckets[i] != null)
result.buckets[i] = buckets[i].deepEntry();
return result;
}catch (CloneNotSupportedException e){
throw new AssertionError();
}
}
克隆复杂对象还有一种方法,先调用super.clone()得到类型正确的对象,然后把所有域都设置成空白状态,然后调用高层的方法重新产生对象的状态。这种做法会产生一个简单、合理且相当优美的clone方法,运行速度稍慢。
总结
1.Cloneable接口是一个失败的接口,它没有提供clone()方法,却影响了Object.clone()克隆的行为:如果类没有实现Cloneable接口,调用super.clone()方法会得到CloneNotSupportedException。
2.所有实现了Cloneable接口的类都应该提供一个公有的方法覆盖clone(),此公有方法首先调用super.clone(),然后修正域,此公有方法一般不应该声明抛出CloneNotSupportedException。
3.如果为了继承而设计的类不应该实现Cloneable接口,这样可以使子类具有实现或者不实现Cloneable接口的自由,就仿佛它们直接扩展了Object一样。父类没有实现Cloneable接口,也没有覆盖clone(),子类如果实现了Cloneable,在覆盖的clone()中调用super.clone()是可以得到正确对象的。
据说很多专家级程序猿从来都不使用clone()方法。
更好的方法
等等,为了实现clone()方法的功能,有必要这么复杂吗?很少有这种必要。为了实现对象拷贝的更好的方法是提供一个拷贝构造器或者拷贝工厂,它们接受这类的一个对象作为参数。
public Yum( Yum yum);//拷贝构造器
public static YumcopyInstance(Yum yum);//拷贝工厂
转载请注明出处:喻红叶《Java中的clone()方法》
分享到:
相关推荐
在Java编程语言中,`clone()`方法是一个非常重要的概念,特别是在对象复制和克隆方面。这个方法来源于`java.lang.Object`类,所有Java类都默认继承了这个方法。本资料"Java中clone方法共6页.pdf.zip"可能包含了关于...
详细的描述了Java中 clone方法使用
Java中的clone方法详解_动力节点Java学院,动力节点口口相传的Java黄埔军校
clone的用法 希望有帮助,仅供参考 通过例子的分析,可以对克隆的方法有所深入了解
### Java中的`clone`方法详解:浅拷贝与深拷贝 #### 一、引言 在Java中,`clone`方法提供了一种快速复制对象的方式。它属于`Object`类的一部分,但需要显式地在子类中声明并实现`Cloneable`接口才能正常使用。本文...
Java的`clone()`方法在软件开发中扮演着重要的角色,特别是在需要复制对象的场景下。在Java中,对象的复制并非像C++等语言中的指针复制那样简单,因为Java中没有指针的概念,而是使用引用。这导致了在默认情况下,...
在Java编程语言中,`Cloneable`接口和`clone()`方法是两个重要的概念,它们用于对象复制。在本文中,我们将深入探讨Java中的浅克隆(shallow clone)和深克隆(deep clone),并结合测试代码进行分析。 首先,让...
本文将深入探讨Java中的`clone`方法,包括其工作原理、使用场景、注意事项以及一些个人实践心得。 首先,让我们理解什么是`clone`。在Java中,`clone`方法主要用于创建一个现有对象的副本,这个副本与原始对象具有...
在Java编程语言中,`clone()`方法是一个非常重要的概念,特别是在对象复制和克隆方面。这个小例子将帮助初学者理解如何在Java中使用`clone()`来创建对象的副本。让我们深入探讨`clone()`方法以及它在实际编程中的...
Java中的clone方法详解 在Java语言中,clone方法是一个非常重要的概念,它允许对象被复制,从而创造出一个新的对象。下面我们将详细介绍Java中的clone方法,并讨论它的实现机制和应用场景。 什么是clone方法 ...
Java 中 clone() 的使用方法 Java 中的 clone() 方法是对象的复制方法,其主要作用是创建一个与原对象相同的新对象。下面将详细介绍 Java 中 clone() 方法的使用方法。 什么是 clone() 方法? clone() 方法是 ...
在Java编程语言中,克隆(Clone)机制是一种创建对象副本的方法,它允许开发者创建一个已有对象的新实例,这个新实例与原对象具有相同的属性值,但却是两个独立的对象,彼此的操作不会互相影响。克隆机制在某些情况...
Java中的`clone`方法是Java语言提供的一种复制对象的方式,它允许创建一个对象的副本,这个副本与原对象具有相同的属性值,但它们是两个独立的对象,修改副本不会影响原对象。`clone`方法存在于Java的`java.lang....
Java中的对象克隆可以通过实现`Cloneable`接口并覆盖`clone()`方法来完成。对象的克隆分为浅拷贝和深拷贝两种形式。 **1. 浅拷贝** 浅拷贝是指创建一个新的对象,然后将原对象的所有非引用类型的成员变量复制到新...
Java中的克隆(Clone)机制是面向对象编程中一种创建对象副本的方法,它允许程序员创建一个已有对象的新实例,新实例的数据与原对象相同。在Java中,克隆分为两种类型:浅克隆(Shallow Clone)和深度克隆(Deep ...
Java中的`clone`方法是Java语言提供的一种复制对象的机制,它允许创建一个现有对象的副本,这个副本具有与原始对象相同的状态,但它们是独立的实体,对其中一个对象的修改不会影响另一个。`clone`方法是Java `Object...
JAVA对象clone方法是JAVA编程语言中的一种复制对象的方法,通过实现Cloneable接口和重写clone方法,可以实现对象的浅复制或深复制。在本文中,我们将通过示例代码详细介绍JAVA对象clone方法的代码实例解析。 首先,...