目录
1. 为什么要使用clone
2. new和clone的区别
3. 复制对象和复制引用的区别
4.浅克隆和深克隆
5. 注意事项
1. 为什么要使用clone
在实际编程过程中,我们常常遇到这种情况:有一个对象 A,需要一个和 A 完全相同新对象 B,并且此后对 B 任何改动都不会影响到 A 中的值,也就是说,A 与 B 是两个独立的对象,但 B 的初始值是由 A 对象确定的。在 Java 语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需
求虽然有很多途径,但实现 clone()方法是其中最简单,也是最高效的手段。
2. new和clone的区别
new
- 通过调用类的构造函数来创建新对象实例,这意味着可以定义对象的初始化状态
- 根据对象类型明确分配相应大小的内存空间
- 需要显式初始化,通常在构造函数中完成
- 涉及内存分配、构造函数调用和初始化,可能在复杂对象中相对较慢
- 不依赖已有对象,每次创建都是全新的实例
- 无需实现任何特殊接口
- 用于全新创建对象实例,无历史状态需求
- 生成完全独立的对象,互不影响
clone
- 不会调用构造函数,而是复制已有对象的属性,适用于快速创建一个具有相似状态的对象
- 分配内存给新对象,并且大小与原对象相同
- 继承了原对象的初始化状态,不需要再次初始化
- 通常认为较快,因为避免了构造函数调用和逐步初始化,但具体取决于JVM实现和场景
- 必须有一个原对象作为克隆的基础
- 需要实现
Cloneable
接口并重写clone()
方法,否则会抛出CloneNotSupportedException
异常 - 用于基于现有对象快速生成一个副本,例如保存当前状态进行操作或比较
- 虽然生成的是一个新对象,但如果涉及到引用类型字段,则需要深拷贝处理,否则可能存在依赖
new操作符的本意是分配内存。程序执行到 new 操作符时,首先去看 new 操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。clone 在第一步是和 new 相似的,都是分配内存,调用 clone 方法时,分配的内存和原对象(即调用 clone 方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone 方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部,new
主要用于创建全新的对象实例,而clone
则用于基于已有对象快速生成副本。选择使用哪一种方式取决于具体的编程需求和场景。理解它们的不同之处能够帮助更好地掌握Java中的对象创建和管理技巧。
3. 复制对象和复制引用的区别
class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}...//此处省略set、get、toString方法
}public class Main {public static void main(String[] args) {Person person1 = new Person("Alice", 30);Person person2 = person1; // 复制引用Person person3 = (Person)person1.clone(); // 复制对象System.out.println("Original Person: " + person1);System.out.println("Reference Copy: " + person2);System.out.println("Object Copy: " + person3);person1.setName("Bob");person1.setAge(40);System.out.println("After modification:");System.out.println("Original Person: " + person1);System.out.println("Reference Copy: " + person2); // 修改了person1,person2也会受到影响System.out.println("Object Copy: " + person3); // person3不受person1的修改影响}
}
在这个例子中,我们创建了一个Person
类,并实例化了三个对象:person1
、person2
和person3
。其中,person2
是通过复制引用的方式创建的,而person3
是通过复制对象的方式创建的。
当我们修改person1
的属性时,可以看到以下结果:
person2
是person1
的引用,因此它们的属性值相同,并且对其中一个对象的修改会影响另一个对象。person3
是通过复制person1
的属性创建的新对象,因此它的属性值与person1
相同,但对person3
的修改不会影响person1
。
复制引用
复制对象
4.浅克隆和深克隆
在Java编程中,clone()
方法提供了一种创建对象副本的方式。通过实现Cloneable
接口并重写clone()
方法,我们可以轻松地创建一个对象的完整复制品。然而,关于使用clone()
方法,存在许多细节和注意事项,尤其是在处理浅克隆(shallow copy)与深克隆(deep copy)时。本篇博客将深入探讨这一主题。
在Java中,所有对象都继承自Object
类,其中包含一个clone()
方法,但该方法是受保护的(protected
),意味着我们不能直接在非子类外部调用它。为了使得clone()
方法可用,我们需要实现Cloneable
接口,该接口是一个标记接口(marker interface),本身不包含任何方法。
实现Cloneable
接口后,我们可以通过重写clone()
方法来提供自定义的复制逻辑。如果不重写clone()
方法,对象将获得一个默认的浅复制行为,这意味着基本字段将被复制,但引用类型字段将只复制引用,而不是引用的对象
浅克隆
浅复制是指在复制对象时,只复制基本类型的字段和引用,但不复制引用的对象。这意味着,如果原对象中的引用类型字段被修改,复制的对象也会受到影响。
class ShallowCopyExample implements Cloneable {int intValue; // 基本类型字段String stringValue; // 引用类型字段public ShallowCopyExample clone() throws CloneNotSupportedException {return (ShallowCopyExample) super.clone();}
}
深克隆
相对地,深复制会复制所有字段,包括引用的对象。这就需要我们自定义clone()
方法以实现所需行为。
class DeepCopyExample implements Cloneable {int intValue; // 基本类型字段String stringValue; // 引用类型字段DeepCopyExample innerObject; // 引用的对象public DeepCopyExample clone() throws CloneNotSupportedException {DeepCopyExample deepCopy = (DeepCopyExample) super.clone();deepCopy.innerObject = new DeepCopyExample(); // 对内部对象进行深复制deepCopy.innerObject.intValue = this.innerObject.intValue;deepCopy.innerObject.stringValue = this.innerObject.stringValue;return deepCopy;}
}
这段代码展示了一个名为DeepCopyExample
的Java类,该类实现了Cloneable
接口。这个类有三个字段:一个基本类型(int)字段intValue
,一个引用类型(String)字段stringValue
,以及一个引用的对象字段innerObject
。
在这个类中,我们重写了clone()
方法,以便实现深拷贝。深拷贝意味着当我们复制一个对象时,不仅复制对象的值,还会复制对象所引用的其他对象。这样,原始对象和克隆对象之间的所有引用都是独立的。
在clone()
方法中,首先调用super.clone()
来创建一个新的DeepCopyExample
对象。然后,我们为innerObject
字段创建一个新的DeepCopyExample
对象,并将原始对象的innerObject
字段的值复制到新对象的相应字段中。最后,返回新创建的深拷贝对象。
需要注意的是,如果innerObject
字段中的DeepCopyExample
对象也包含其他引用类型的字段,那么这些字段也需要进行深拷贝。在这个示例中,我们只处理了intValue
和stringValue
字段的深拷贝
5. 注意事项
Cloneable
接口的存在是为了标记对象可以被克隆。如果我们忘记实现这个接口,clone()
方法会抛出CloneNotSupportedException
异常。- 对于含有引用类型字段的对象,默认的
clone()
方法不会创建新的对象实例来复制这些字段,而是复制它们的引用,这可能导致意外的副作用。 - 在实现深复制时,需要特别关注循环引用的情况,即一个对象直接或间接引用了自己。不妥善处理这种情况会导致无限递归和栈溢出错误。
- 当对象层次结构很复杂时,手动实现深复制可能会很繁琐,此时可以考虑使用序列化或其他第三方库来实现深复制。