实例说明Java中的浅拷贝和深拷贝

看到集合中的类都继承了Cloneable接口,便学习了Java中的浅拷贝和深拷贝.

简单变量复制

int num1 = 5;
int num2 = 0;
num1 = num2;
System.out.println("num1 = " + num1 +  "   num2 = " + num2);

num2 = 112;
System.out.println("num1 = " + num1 +  "   num2 = " + num2);

输出

num1 = 0   num2 = 0
num1 = 0   num2 = 112

很显然,这里是对其值进行了复制,当对num2进行更改时,num1直不会进行改变.所有的基本类型(boolean,char,byte,short,float,double.long)以及其包装类型和String都是如此.
假如将上面的基本类型换成其包装类型Integer,结果是一样的,但是本质上有区别
当执行Integer类型的num2 = 112;,创建的是一个新的对象Integer(112),

对象复制

创建Student类

class Student { 
    private int number; 
 
    public int getNumber() { 
        return number; 
    } 
 
    public void setNumber(int number) { 
        this.number = number; 
    }     
} 

编写测试代码

Student stu1 = new Student(); 
stu1.setNumber(12345); 
Student stu2 = stu1; 
 
System.out.println("学生1:" + stu1.getNumber()); 
System.out.println("学生2:" + stu2.getNumber()); 

stu1.setNumber(1234);

System.out.println("学生1:" + stu1.getNumber()); 
System.out.println("学生2:" + stu2.getNumber()); 

System.out.println("stu1和stu2是同一个对象么?" +  (stu1 == stu2)); // true

输出

学生1:12345
学生2:12345
学生1:1234
学生2:1234
stu1和stu2是同一个对象么?true

从上面结果可以看出
当执行stu1.setNumber(1234);后,stu2的number也更改为1234.
但这就存在问题了,因为我们需要的是对象内属性的复制,但是使用Student stu2 = stu1; 后,两者对象貌似关联到一起了.
原因是执行Student stu2 = stu1; 时,是将stu2的引用指向了stu1内存地址,两者的内存地址一致,因此当更改其中一个对象的属性值时,另一个也会跟着改变.

使用clone()进行对象复制(浅拷贝实现)

在这里我们使用clone进行拷贝,clone() 是Object中定义的protected方法,对继承类可见,我们需要重写它.重写类要实现Cloneable接口,否则会报CloneNotSupportedException异常.Cloneable是一个空的接口,并没有任何接口方法.

protected native Object clone() throws CloneNotSupportedException;
public interface Cloneable {
}
class Apple  implements Cloneable{ 
    //需要实现Cloneable
    private int number; 
 
    public int getNumber() { 
        return number; 
    } 
 
    public void setNumber(int number) { 
        this.number = number; 
    }  
    //重写clone()
    @Override
    public Object clone() {
    	Apple apple =  null;
    	try {
    		apple =  (Apple)super.clone();    		
    		return apple;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
    	return apple;
    }
} 


测试

Apple apple1 = new Apple(); 
apple1.setNumber(12345); 
Apple apple2;
apple2 = (Apple)apple1.clone();
System.out.println("苹果1:" + apple1.getNumber()); 
System.out.println("苹果2:" + apple2.getNumber()); 
apple1.setNumber(1234);

System.out.println("苹果1:" + apple1.getNumber()); 
System.out.println("苹果2:" + apple2.getNumber()); 

System.out.println("apple1和apple2是同一个对象么?" +  (apple1 == apple2)); // false


输出

苹果1:12345
苹果2:12345
苹果1:1234
苹果2:12345
apple1和apple2是同一个对象么?false

从输出结果上看,已经实现了对象的拷贝,当执行apple1.setNumber(1234);时,apple2的值并没有进行改变.

但是事情到这里就结束了么??

Apple中的属性都为基本数据类型,当存在引用对象时,结果会怎么表现??

对包含引用对象的对象使用clone进行复制

创建包含引用对象的类,该类中包含了People引用属性


class House  implements Cloneable{ 
    private int number; 
    private People people;
    
    public People getPeople() {
		return people;
	}

	public void setPeople(People people) {
		this.people = people;
	}

	public int getNumber() { 
        return number; 
    } 
 
    public void setNumber(int number) { 
        this.number = number; 
    }  
    @Override
    public Object clone() {
    	House house =  null;
    	try {
    		house =  (House)super.clone();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}

    	//house.people = (People)people.clone();
    	return house;
    }
    
    @Override 
    public String toString() {
    	return ("number = " + number
    			+ "   name = " + people.getName());
    }
    
} 

创建People类

class People  { 
    private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	   
} 

编写测试

People people = new People();
people.setName("张三");

 House house1 = new House(); 
 house1.setPeople(people);
 house1.setNumber(12345); 
 House house2;

house2 = (House)house1.clone();
 System.out.println("房子1:" + house1.toString()); 
 System.out.println("房子2:" + house2.toString()); 
 
 System.out.println("修改house1....");
 house1.setNumber(2222);
 people.setName("李四");
 
 System.out.println("房子1:" + house1.toString()); 
 System.out.println("房子2:" + house2.toString()); 

 System.out.println("house1和house2是同一个对象么?" +  (house1 == house2)); // false

输出

房子1:number = 12345   name = 张三
房子2:number = 12345   name = 张三
修改house1....
房子1:number = 2222   name = 李四
房子2:number = 12345   name = 李四
house1和house2是同一个对象么?false

从输出来看,当执行完以下语句后
house1.setNumber(2222);
people.setName(“李四”);

house2的number值未改变,因为我们已经对其实现了浅拷贝,但是people的name改变了.
原因是浅复制只是复制了people变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象

深拷贝实现

因此如果需要实现真正的复制对象,也需要将复制对象内的引用变量实现可复制话,需要对这两个类进行改写

People也重写clone方法

class People implements Cloneable { 
    private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	@Override
    public Object clone() {
		People people =  null;
    	try {
    		people =  (People)super.clone();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
       finally {
	   }
    	return people;
    }       
} 

对House类的clone进行修改

 @Override
 public Object clone() {
  	House house =  null;
  	try {
  		house =  (House)super.clone();
} catch (Exception e) {
	// TODO: handle exception
	e.printStackTrace();
}
  //people也进行复制
  	house.people = (People)people.clone();
  	return house;
  }

输出

房子1:number = 12345   name = 张三
房子2:number = 12345   name = 张三
修改house1....
房子1:number = 2222   name = 李四
房子2:number = 12345   name = 张三
house1和house2是同一个对象么?false

可以看出对house1的name进行修改后house2的name 并没有实现更改.已经实现了深度复制.

总结

**基本对象拷贝:**执行拷贝后,两个对象都指向同一个内存空间,改变其中一个对象的属性值时,另一个也跟着改变

浅拷贝: 是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝,没有对引用指向的对象进行拷贝。也就是说两个对象内的引用类型变量仍然只指向同一快内存区域.需要实现Cloneable接口

深拷贝: 是指在拷贝对象时,同时会对引用指向的对象进行拷贝。区别就在于是否对 对象中的引用变量所指向的对象进行拷贝.其本身以及内部的引用变量需要实现Cloneable接口.

在java中 clone为什么要用super.clone()方法 这里为什么要用super不是this?

1.Object中的clone执行的时候使用了RTTI(run-time type identification)的机制,动态得找到目前正在调用clone方法的那个reference,根据它的大小申请内存空间,然后进行bitwise的复制,将该对象的内存空间完全复制到新的空间中去,从而达到shallowcopy的目的。
所以你调用super.clone() 得到的是当前调用类的副本,而不是父类的副本。根本没有必用调用this.clone();
2.要让实例调用clone方法就需要让此类实现Cloneable接口,API里面还有句话是:如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常,这便是“合法”的含义。 但请注意,Cloneable接口只是个标签接口,不含任何需要实现的方法,就像Serializable接口一样。

总之,一般如果你的子类没有特殊需要而重写clone()方法就直接用super.clone() 就行了。

super.clone()的操作是:(还是以Object中的clone()来说明吧
<1>检查当前对象this所属的类(指:你的Strings类有没有实现Cloneable接口,若没有则抛出:CloneNotSupportedException
<2>创建当前对象this所属的类(即你的Strings类)的一个对象(称为:copy_object),并对该copy_object进行初始化,使得copy_object的数据成员值与当前对象this中数据成员值一模一样(即:当前对象this的二进制拷贝)–这就是所谓的"浅复制". 对于你的代码,即:copy_object对象中的数据成员private String str; 与被复制的当前对象this中的private String str 引用的是同一个字符串对象.由于:字符串对象是内部值不可改变的常对象,因而:对于浅复制,这是没有问题的.使用效果与"深复制"相同. 同样:基本数据类型的数据成员,"浅复制"与"深复制"使用效果也相同.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值