我们要获得一个对象的副本时,用new创建对象再对各个域赋值,还不如直接对象克隆。Java的对象克隆有浅克隆和深克隆之分。但是不管怎么样,我们都要实现Object类的clone()方法。
Object类的clone()方法
当需要克隆对象时,需要使用Object类的clone()方法,该方法的声明如下:
protected native Object clone() throws CloneNotSupportedException;
通常我们继承Cloneable接口实现这个方法,我们要把这个方法的权限变成public。
浅克隆
我们编写2个类,分别是Dog和Cat,在Cat中声明Dog,然后我们克隆Cat,发现Dog没有被克隆,这就是浅克隆,不能克隆引用对象。
代码:
public class Dog {
static final long serialVersionUID = 1L;
private String name;
private String age;
public Dog(String name, String age) {
this.name = name;
this.age = age;
}
//.....set和get方法
}
Cat:
public class Cat implements Cloneable {
private Dog d;
private String name;
public Cat(Dog d, String name) {
this.d = d;
this.name = name;
}
//.....set和get方法
@Override
public Cat clone(){
Cat cat=null;
try{
cat=( Cat)super.clone();
}catch (Exception e){
e.printStackTrace();
}
return cat;
}
}
测试:
public class test {
public static void main (String arg[]) throws Exception{
Dog d=new Dog("xiao","16");
Cat c=new Cat(d,"ming");
Cat c1=c.clone();
System.out.println("2个对象是否相同:");
System.out.println(c1==c);
System.out.println("改变c1,看看会影响c对象吗?");
c1.setName("c1的ming");
c1.getD().setAge("c1的16");
System.out.println("输出c:");
System.out.println("name:"+c.getName());
System.out.println("Dog的age:"+c.getD().getAge());
}
}
结果:
2个对象是否相同:
false
改变c1,看看会影响c对象吗?
输出c:
name:ming
Dog的age:c1的16
我们从结果看出:这2个对象是不一样的,改变克隆对象的name不能改变c的对象,说明c1和c对象的name不一样,但是我们改变c1的Dog,也改变了c的Dog对象,说明克隆对象中的引用对象和被克隆对象中的引用对象是同一个。则这就是浅克隆,只能克隆除了基本类型和String的域,而引用对象还是同一个。
Java对象的深克隆
正如浅克隆不能克隆引用对象,如果连引用对象都能克隆,这就是深克隆。对于深克隆,我们有2种方法,分别:将引用类型也进行克隆或者用序列化反序列化进行克隆。
将引用类型也进行深克隆
对于上面的浅克隆,我们只要将Dog也克隆就行
Dog类
public class Dog implements Cloneable{
private String name;
private String age;
public Dog(String name, String age) {
this.name = name;
this.age = age;
}
//.....set和get方法
@Override
public Dog clone(){
Dog dog=null;
try{
dog=( Dog)super.clone();
}catch (Exception e){
e.printStackTrace();
}
return dog;
}
}
Cat类:
public class Cat implements Cloneable {
private Dog d;
private String name;
public Cat(Dog d, String name) {
this.d = d;
this.name = name;
}
//.....set和get方法
@Override
public Cat clone(){
Cat cat=null;
try{
cat=( Cat)super.clone();
cat.d= d.clone();
}catch (Exception e){
e.printStackTrace();
}
return cat;
}
}
测试:
public class test {
public static void main (String arg[]) throws Exception{
Dog d=new Dog("xiao","16");
Cat c=new Cat(d,"ming");
Cat c1=c.clone();
System.out.println("2个对象是否相同:");
System.out.println(c1==c);
System.out.println("改变c1,看看会影响c对象吗?");
c1.setName("c1的ming");
c1.getD().setAge("c1的16");
System.out.println("输出c:");
System.out.println("name:"+c.getName());
System.out.println("Dog的age:"+c.getD().getAge());
}
}
结果:
2个对象是否相同:
false
改变c1,看看会影响c对象吗?
输出c:
name:ming
Dog的age:16
我们从结果看出:这2个对象是不一样的,重点在于我们改变了c1的Dog的值没有影响c的Dog。如果引用类型中还有可变的引用类型域的话,则该域也是需要克隆的,如Cat类的d,也是要克隆的。
用序列化反序列化进行深克隆
对于上面的深克隆方法来说,如果一二个引用对象并不复杂,但是如果很多引用对象,就要一 一的克隆,这就很麻烦了,于是有了序列化反序列化进行深克隆。
序列化可以把对象变成二进制流写入文件中,然后用反序列化读出对象就是一个新的对象了。当然对于克隆我们不需要保存,所以我们保存在二进制流数组中就行了。对于序列化和反序列化的文章可以看这篇:Java序列化和反序列化与transient关键字
代码:
Dog:
public class Dog implements Serializable{
private static final long serialVersionUID = 42L;
private String name;
private String age;
public Dog(String name, String age) {
this.name = name;
this.age = age;
}
//.....set和get方法
}
Cat:
public class Cat implements Cloneable, Serializable {
private static final long serialVersionUID = 42L;
private Dog d;
private String name;
public Cat(Dog d, String name) {
this.d = d;
this.name = name;
}
//.....set和get方法
@Override
public Cat clone(){
Cat cat=null;
ByteArrayOutputStream bs=new ByteArrayOutputStream();
try{
ObjectOutputStream out=new ObjectOutputStream(bs);
out.writeObject(this);
out.close();
}catch (Exception e){
e.printStackTrace();
}
ByteArrayInputStream is=new ByteArrayInputStream(bs.toByteArray());
try{
ObjectInputStream in=new ObjectInputStream(is);
cat=(Cat) in.readObject();
in.close();
}catch (Exception e){
e.printStackTrace();
}
return cat;
}
}
测试:
public class test {
public static void main (String arg[]) throws Exception{
Dog d=new Dog("xiao","16");
Cat c=new Cat(d,"ming");
Cat c1=c.clone();
System.out.println("2个对象是否相同:");
System.out.println(c1==c);
System.out.println("改变c1,看看会影响c对象吗?");
c1.setName("c1的ming");
c1.getD().setAge("c1的16");
System.out.println("输出c:");
System.out.println("name:"+c.getName());
System.out.println("Dog的age:"+c.getD().getAge());
}
}
结果:
2个对象是否相同:
false
改变c1,看看会影响c对象吗?
输出c:
name:ming
Dog的age:16
我们从结果看出:这2个对象是不一样的,重点在于我们改变了c1的Dog的值没有影响c的Dog。对于任何一个序列化的对象,都是要求实现Serializable接口,如果该对象中还有引用对象,这个引用对象也要实现Serializable接口,但是这个方法会比上面的方法慢
原创来源:滴一盘技术