1. 克隆的用处
在日常编码中我们经常需要产生某个对象的副本,这里的副本并不是指向同一个对象的不同引用,而是与当前对象状态一模一样的另一个新的对象。如果使用单纯的引用赋值,会发生什么效果呢?
我们可以观察下面的代码:
package com.coderap.foundation.clone;
class Address {
public String province;
public String city;
public Address(String province, String city) {
this.province = province;
this.city = city;
}
@Override
public String toString() {
return “Address{”
“province='” province ‘”
“, city='” city ‘”
‘}’;
}
}
class Person {
public String name;
public Integer age;
public Address address;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
}
public class CloneTest {
public static void main(String[] args) {
Person person = new Person(“Tom”, 20);
person.address = new Address(“CA”, “Los Angeles”);
System.out.println(“before: ” person.name);
System.out.println(“before: ” person.age);
System.out.println(“before: ” person.address);
Person newPerson = (Person) person;
person.name = “Jack”;
person.age = 22;
newPerson.address.province = “CA”;
newPerson.address.city = “Nevada”;
System.out.println(“after: ” person.name);
System.out.println(“after: ” person.age);
System.out.println(“after: ” person.address);
System.out.println(“person equal newPerson ? ” person.equals(newPerson));
}
}
在上面的代码中我们单纯地将一个新的引用指向一个已有的对象,然后使用新的引用对对象进行操作,可以发现,所有的更改在两个引用上都体现出来了:
before: Tom
before: 20
before: Address{province=’CA’, city=’Los Angeles’}
after: Jack
after: 22
after: Address{province=’CA’, city=’Nevada’}
person equal newPerson ? true
在Java中,两个引用同时指向相同的对象时,这两个引用是指向的同一块内存,所以使用任何一个引用对内存的操作都将直接反映到另一个引用上,单纯的引用赋值是不能够克隆对象的。为了解决克隆问题,Java提供了Cloneable接口和clone()方法。
2. Cloneable 接口和 clone 方法
Cloneable接口是一个标记接口,其中没有任何内容,定义如下:
package java.lang;
public interface Cloneable {}
clone()方法是在Object类中定义的:
protected native Object clone() throws CloneNotSupportedException;
clone()方法是被protected修饰的受保护的方法,类只有实现了Cloneable接口,才可以在该类的实例上调用clone()方法,否则会抛出CloneNotSupportException异常。
Object的clone()方法创建并返回此对象的一个副本。对于任何对象o,clone()方法有以下的规则:
o.clone() != o为true;
o.clone().getClass() == o.getClass()为true;
o.clone().equals(o)一般情况下为true,但这并不是必须要满足的要求。
Object中默认的实现是一个浅克隆,但是该方法是有缺陷的,如果需要实现深层次克隆的话,必须对类中可变域生成新的实例。
2.1. 浅克隆
浅克隆并不会把对象所有属性全部克隆一份,而是有选择性的克隆,克隆规则如下:
基本类型。如果变量是基本类型,则克隆其值,比如int、float、long等。
String字符串,对于字符串的克隆比较特殊,克隆的是引用地址,但是在修改的时候,它会从字符串池(String Pool)中重新生成新的字符串,原有的字符串对象保持不变,此处可以认为String是个基本类型。
对象。如果变量时一个实例对象,则克隆地址引用,也就是说此时新克隆出的对象与原有对象共享该实例变量,不受访问权限的限制。这中克隆操作是非常危险的,意味着不同的对象之间对某些引用对象是共有的,相互修改将受到影响。
注1:基本数据类型在克隆时是进行的原值克隆。
如下面的代码,我们只是简单的在Person类中实现了Cloneable接口并且重写了clone()方法,同时进行克隆操作:
package com.coderap.foundation.clone;
class Address {
public String province;
public String city;
public Address(String province, String city) {
this.province = province;
this.city = city;
}
@Override
public String toString() {
return “Address{”
“province='” province ‘”
“, city='” city ‘”
‘}’;
}
}
class Person implements Cloneable {
public String name;
public Integer age;
public Address address;
public Person() {
System.out.println(“Person() execute”);
}
public Person(String name, Integer age) {
System.out.println(“Person(String name, Integer age) execute”);
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person(“Tom”, 20);
person.address = new Address(“CA”, “Los Angeles”);
System.out.println(“before: ” person.name);
System.out.println(“before: ” person.age);
System.out.println(“before: ” person.address);
Person newPerson = (Person)person.clone();
newPerson.name = “Jack”;
newPerson.age = 22;
newPerson.address.province = “CA”;
newPerson.address.city = “Nevada”;
System.out.println(“after: ” person.name);
System.out.println(“after: ” person.age);
System.out.println(“after: ” person.address);
System.out.println(“person != newPerson ? ” String.valueOf(person != newPerson));
System.out.println(“person getClass equal newPerson getClass ? ” person.getClass().equals(newPerson.getClass()));
System.out.println(“person equal newPerson ? ” person.equals(newPerson));
}
}
运行上面的代码,可以得到打印信息如下:
Person(String name, Integer age) execute
before: Tom
before: 20
before: Address{province=’CA’, city=’Los Angeles’}
after: Tom
after: 20
after: Address{province=’CA’, city=’Nevada’}
person != newPerson ? true
person getClass equal newPerson getClass ? true
person equal newPerson ? false
我们可以得出以下结果:
克隆一个对象不会重复调用对应类的构造方法;
上述最后的三条的判断的结果是遵循了clone()方法三条规则的;
基本类型和String类型的数据都是独立的,并不会收到新对象的影响,但是引用类型的对象会受到新对象的影响。
需要注意的是,在修改城市信息时,如果我们直接指定newPerson.address = new Address(“CA”, “Nevada”)其实是不会影响到原来的person对象的,因为虽然newPerson和person的address指向的同一个Address对象,但使用newPerson.address = new Address(“CA”, “Nevada”)会给newPerson对象生成一个新的Address对象,并将newPerson的address引用指向这个新的对象,所以并不会影响到原有的person对象的address对象属性。
Java中实现了Cloneable接口的类有很多,如ArrayList、Calendar、Date、HashMap、Hashtable、HashSet、LinkedList等等。我们在使用这些类时并不需要考虑浅克隆带来的影响。
2.2. 深克隆
深克隆操作应该将除自身对象以外的所有对象,包括自身所包含的所有对象实例都进行克隆。
其实Object的clone()方法提供的是一种浅克隆的机制,如果想要实现对对象的深克隆,有两种办法:
先对对象进行序列化,紧接着马上反序列化出;
先调用super.clone()方法克隆出一个新对象来,然后在子类的clone()方法中手动给克隆出来的非基本数据类型(引用类型)赋值,比如ArrayList的clone()方法:
/**
* Returns a shallow copy of this ArrayList instance. (The
* elements themselves are not copied.)
*
* @return a clone of this ArrayList instance
*/
public Object clone() {
try {
ArrayList> v = (ArrayList>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn’t happen, since we are Cloneable
throw new InternalError(e);
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/182124.html原文链接:https://javaforall.cn