Clone()
方法是 Object 类提供的方法,顾名思义,通过调用改方法可以拷贝/克隆一个对象,作为 Java 语言的一个基本特性,在面试和工作中会经常遇到。方法签名如下:
protected native Object clone() throws CloneNotSupportedException;
使用方法
一个对象要使用 clone()
方法来克隆对象时,改对象要继承 Cloneable
接口,这个接口是一个标记接口,然后重写 Object 对象的 clone()
方法,并设置方法的访问修饰符为 public
。在重写的 clone()
方法中调用父类的 clone()
方法也就是 Object 的 clone()
方法,该方法的返回值是 Object 类型,所以我们要强制类型转换成我们期待的类型。
public class Person implements Cloneable{
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public Person clone() {
try {
return (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
深拷贝和浅拷贝
Java 中的对象拷贝分为深拷贝和浅拷贝,所谓浅拷贝,就是使用上述方法生成的拷贝对象。
- 浅拷贝:基本数据类型的成员变量拷贝值,引用类型的成员变量拷贝引用地址
- 深拷贝:基本数据类型的成员变量拷贝值,引用类型的成员变量递归的调用
clone()
方法
clone()
方法返回的对象总是一个新的对象,当时如果是浅拷贝,对象中的引用类型变量和被拷贝对象中的是同一个对象,而深拷贝中的引用类型变量会是一个新的对象。
通过简单的测试代码即可看出其中的差别:
public class Person implements Cloneable{
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public Person clone() {
try {
return (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class Teacher implements Cloneable{
public Person person;
public int id;
public String schoolName = "清华大学";
public Teacher(Person person, int id, String schoolName) {
this.person = person;
this.id = id;
this.schoolName = schoolName;
}
@Override
public String toString() {
return "Teacher{" +
"person=" + person +
", id=" + id +
", schoolName='" + schoolName + '\'' +
'}';
}
@Override
public Teacher clone() {
try {
return (Teacher) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class DeepCloneTeacher extends Teacher {
public DeepCloneTeacher(Person person, int id, String schoolName) {
super(person, id, schoolName);
}
@Override
public Teacher clone() {
Teacher teacher = super.clone();
teacher.person = person.clone();
return teacher;
}
}
@Test
public void test02(){
// 浅拷贝
Teacher teacher = new Teacher(new Person("小红", 12), 50, "清华大学");
Teacher clone = teacher.clone();
System.out.println("teacher == clone: " + (teacher == clone));
System.out.println("teacher.person == clone.person: " + (teacher.person == clone.person));
System.out.println("teacher.toString(): " + teacher.toString());
System.out.println("clone.toString(): " + clone.toString());
System.out.println("=================================");
// 深拷贝
Teacher deepCloneTeacher = new DeepCloneTeacher(new Person("小红", 12), 50, "清华大学");
Teacher clone2 = deepCloneTeacher.clone();
System.out.println("deepCloneTeacher == clone2: " + (deepCloneTeacher == clone2));
System.out.println("deepCloneTeacher.person == clone2.person: " + (deepCloneTeacher.person == clone2.person));
System.out.println("deepCloneTeacher.toString(): " + deepCloneTeacher.toString());
System.out.println("clone2.toString(): " + clone2.toString());
}
输出:
teacher == clone: false
teacher.person == clone.person: true
teacher.toString(): Teacher{person=Person{name='小红', age=12}, id=50, schoolName='清华大学'}
clone.toString(): Teacher{person=Person{name='小红', age=12}, id=50, schoolName='清华大学'}
=================================
deepCloneTeacher == clone2: false
deepCloneTeacher.person == clone2.person: false
deepCloneTeacher.toString(): Teacher{person=Person{name='小红', age=12}, id=50, schoolName='清华大学'}
clone2.toString(): Teacher{person=Person{name='小红', age=12}, id=50, schoolName='清华大学'}
数组对象的 clone()
在 Java 中,数组也是一个对象,那么它也可以调用 clone()
方法来拷贝自己,对于以下几种情景它的行为会是怎么样被?
-
一维基本数据类型数组是深拷贝还是浅拷贝
-
多维基本数据类型数组是深拷贝还是浅拷贝
-
一维引用数据类型数组是深拷贝还是浅拷贝
测试代码如下:
@Test
public void test01() {
// 引用类型一维数组
Person[] persons = new Person[2];
persons[0] = new Person("小红", 15);
persons[1] = new Person("小名", 20);
Person[] clone = persons.clone();
System.out.println("persons == clone:" + (persons == clone));
System.out.println("persons[0] == clone[0]:" + (persons[0] == clone[0]));
System.out.println("persons.toString():" + Arrays.toString(persons));
System.out.println("clone.toString():" + Arrays.toString(clone));
System.out.println("==============================");
// 基本数据类型一维数组
int[] ints = new int[2];
ints[0] = 1;
ints[1] = 2;
int[] clone1 = ints.clone();
System.out.println("ints == clone1:" + (ints == clone1));
System.out.println("ints[0] == clone1[0]:" + (ints[0] == clone1[0]));
System.out.println("ints.toString():" + Arrays.toString(ints));
System.out.println("clone1.toString():" + Arrays.toString(clone1));
System.out.println("==============================");
// 基本数据类型二维数组
int[][] intss = new int[2][2];
intss[0][0] = 0;
intss[0][1] = 1;
intss[1][0] = 2;
intss[1][1] = 3;
int[][] clone2 = intss.clone();
System.out.println("intss == clone2:" + (intss == clone2));
System.out.println("intss[0] == clone2[0]:" + (intss[0] == clone2[0]));
System.out.println("intss.toString():");
for (int[] intss1 : intss) {
System.out.println(Arrays.toString(intss1));
}
System.out.println("clone2.toString():");
for (int[] ints1 : clone2) {
System.out.println(Arrays.toString(ints1));
}
}
输出:
persons == clone:false
persons[0] == clone[0]:true
persons.toString():[Person{name='小红', age=15}, Person{name='小名', age=20}]
clone.toString():[Person{name='小红', age=15}, Person{name='小名', age=20}]
==============================
ints == clone1:false
ints[0] == clone1[0]:true
ints.toString():[1, 2]
clone1.toString():[1, 2]
==============================
intss == clone2:false
intss[0] == clone2[0]:true
intss.toString():
[0, 1]
[2, 3]
clone2.toString():
[0, 1]
[2, 3]
由输出结果可知:
- 数组对象的
clone()
方法的行为和其他对象并无差别,即都是浅拷贝 - 当数组是一维基本数据类型的时候,简单的浅拷贝即可实现拷贝对象的需求
- 当数组是一维引用数据类型的时候,因为是浅拷贝,所以拷贝数组中的元素和被拷贝数据中的元素是同一个对象
- 当数组是二维基本数据类型的时候,二维数组相当于是一维数组的数组,而数组是引用数据类型,所以二维基本数据类型
clone()
方法的行为相当于一个一维引用数据类型的行为
拷贝构造器和拷贝工厂
除了使用 clone()
我们还有其他拷贝对象的方法吗?有。
在 《Effective Java》 中提到
对象拷贝的更好的方法是提供一个拷贝构造器或拷贝工厂。拷贝构造器就是一个参数为类的构造器。例如:
public Yum(Yum yum{...})
拷贝工厂是类似于拷贝构造器的静态工厂:
public static Yum newInstance(Yum yum){...}
在 Java 集合框架中,所有通用集合实现都提供了一个拷贝构造器,其参数类型为 Collection
或者 Map
接口,比如 new HashMap(Map map)
。