【06_1】java | clone()函数深度解析及深浅克隆的实现与堆地址的关系

clone()函数定义在Object类中:它位于java.lang包中

他是java中的特殊函数:

 protected native Object clone() throws CloneNotSupportedException;

protect: 表示clone()的访问权限是保护的;由于他是protected的,再加之它位于java.lang包中,而此包是jdk提供的包,不能修改的,所以我们一般不会将自己的运用程序的代码放在此目录下,所以clone()函数一般是不可见的遵守protected访问修饰符的规则可参考:protected的访问控制。所以最原始的clone()函数(即由Object声明)一般只会出现在下面两种情况:

  1. 在类定义时重写clone()函数;
  2. 用在其他成员方法的定义中。其实在原理上和上面时一样的。关于实现细节将在后面讲解
    native: 表示clone这个函数的实现不是通过java语言实现的;
    throws CloneNotSupportedException:表示此clone函数可以抛出CloneNotSupportedException异常;

所以,虽然Object类中定义了clone方法,但是,如果没有在类的定义中重写它,或者将其放在其他函数的实现中,则无法使用clone()的功能, 所以接下来,主要讲清如何让自定义的类可以使用clone()的功能,对clone()进行重写和将其放在其他成员函数中是类似 的

  • 如果一个类使用,要获得clone()方法,则必须对clone()方法重写。
  • 需要重写clone()的类必须实现Cloneable接口,同时还要捕捉CloneNotSupportedException

实现克隆重写的基本结构(很实用):

public Text implements Cloneable{
	//Text的其他结构代码
	//下面是重写 clone()函数的代码,实现的是最基本的浅克隆即实现最外层的数据克隆
	public Text clone(){
		Text clone = null; //clone为克隆对象的引用
		try	{
			clone = (Name)super.clone(); //浅克隆返回克隆对象
		}catch(CloneNotSupportedException e){
			System.err.println(e.toString);
		}//try.....至此是捕获异常,及处理异常,必不可少,此部分也是基本框架
		/*
		如果要进行更深层此的克隆,则可以从此出开始处理,实现深克隆
		*/
	
		return clone;
	}
}

在上面的基本代码框架中,实现的是浅克隆。 实际上,所谓浅克隆实现的结果就是通过super.clone()新建一个Text类的匿名对象( 显然这个对象所在的空间和调用super.clone()函数的实例的堆空间不同的),由于clone函数的操作语言不是java,应该是C语言,所以clone()可以获得实例本身的地址,通过类本身的信息(类的结束位置,及变量的数据类型)及指针偏于,完全将实例本身的空间的数据,赋值给匿名对象所在的空间。
等等
那为什么不是通过super.clone()的调用,而内部实现的操作是对象本身通过实例名.成员名来进行复制呢?原因是由于类的结构并不是唯一的,固定的,不同的类之间千差万别,所以super.clone()根本无法对类的结构进行统一的描述,加之clone并不是最好的解决问题的办法或者说在需要时也可以通过其他方法实现同样的目的,所有,java开发者也不会把精力用在clone的问题上。


回过头来
最终用clone引用指向这个匿名变量,这就是所谓的浅克隆!!
然在内存是上,浅克隆的引用与原实例的引用是完全不同的,但是,但是,他们之间有时是相互影响的,例如对克隆的操作,可能会影响到原实例的值。
而所谓深克隆,就是克隆之后对两者之间的操作永远不会互相影响!!


  • 如果类A的数据成员是基本类型,浅克隆之后,双方对基本类型的成员的操作互不影响,即如果类A的数据成员中只有基本数据类型,深克隆等价于浅克隆,例:
package text;

class A implements Cloneable {
	int a;
	int b;
	public static void main(String[] args){
		A aA = new A(1, 2); //实例a
		A bA = aA.clone(); //由a克隆浅克隆而来的b
		//输出aA和bA
		aA.showA();
		aA.showA();
		//修改aA的值
		aA.setA(3, 4);
		//输出
		aA.showA();
		bA.showA();
	}
	
	A(int a, int b){//构造函数
		this.a = a;
		this.b = b;
	}
	void setA(int a, int b){
		this.a = a;
		this.b = b;
	}
	void showA(){
		System.out.print("a = " + a);
		System.out.println("\tb = " + b);
	}
	public A clone(){//重写克隆
		A clone = null;
		try	{
			clone = (A)super.clone(); 
		}catch(CloneNotSupportedException e){
			System.err.println(e.toString);
		}
		return clone;
	}
}
//输出结果:
a = 1	b = 2
a = 1	b = 2
a = 3	b = 4
a = 1	b = 2
	//所以此时浅拷贝相当于深拷贝

  • 如果类A的数据成员除了基本数据类型,只有字符串类型,数组类型 或者自定义类类型类B,但此时的自定义类类型类B中数据成员只有基本数据类型,则类A的浅克隆等价于深克隆;例:
package text;
class B{//类B中只有基本数据成员
	private int a = 1;
	private int b = 1;
	void setB(int a, int b){
		this.a = a;
		this.b = b;
	}
	void showB(){
		System.print("in class B part : " + a "\t" + b);
}

class A implements Cloneable {
	String str;
	B bB;
	public static void main(String[] args){
		A aA = new A(); //实例a
		A bA = aA.clone(); //由a克隆浅克隆而来的b
		//输出aA和bA
		aA.showA();
		aA.showA();
		//修改aA的值
		aA.setA("jjf_1, 2, 2);
		//输出
		aA.showA();
		bA.showA();
	}
	
	A(){//构造函数
		str = "jjf";
		bB = new B()
	}
	void setA(string str, int a, int b){
		this.str = str;
		bB.setB(a, b);
	}
	
	void showA(){
		System.out.print("str = " + str + "\t")
		System.out.println(bB.showB);
	}
	public A clone(){//重写克隆
		A clone = null;
		try	{
			clone = (A)super.clone(); 
		}catch(CloneNotSupportedException e){
			System.err.println(e.toString);
		}
		return clone;
	}
}

//输出结果为
jjf	    in class B part : 1	    1
jjf	    in class B part : 1	    1
jjf_1	  in class B part : 2	  2
jjf	    in class B part : 1	    1

//由结果可以看出,此时浅拷贝相当于深拷贝

  • 如果类B的数据成员除了上述所说的成员外,还有其他数据成员C,但类C中的数据成员的类型是有引用类型(类,字符串,数组),则在浅拷贝中,A中对应于C中该部分的引用类型的修改会使拷贝实例和原实例相互影响。
    下面的例子是C中的数据成员包含字符串:(类或数组结果一样)
package text;
class B{//类B中只有基本数据成员
	private String str;
	B(String str){
		thsi.str = str;
	}
	void setB(String str){
		this.str = str;
	}
	void showB(){
		System.print("in class B part : " + s);
}

class A implements Cloneable {
	String str;
	B bB;
	public static void main(String[] args){
		A aA = new A(jjf, zxm); //实例a
		A bA = aA.clone(); //由a克隆浅克隆而来的b
		//输出aA和bA
		aA.showA();
		aA.showA();
		//修改aA的值
		aA.setA("jjf_1, zxm_1);
		//输出
		aA.showA();
		bA.showA();
	}
	
	A(String str1, String str2){//构造函数
		str = str1;
		bB = new B(str2);
	}
	void setA(string str1, String str2){
		str = str1;
		bB.setB(str2);
	}
	
	void showA(){
		System.out.print("str = " + str + "\t")
		System.out.println(bB.showB);
	}
	public A clone(){//重写克隆
		A clone = null;
		try	{
			clone = (A)super.clone(); 
		}catch(CloneNotSupportedException e){
			System.err.println(e.toString);
		}
		return clone;
	}
}
//输出结果为:
jjf	    in class B part : zxm
jjf	    in class B part : zxm
jjf_1	  in class B part : zxm_1
jjf     in class B part : zxm_1

由输出结果可以看到bA.bB.str因为通过aA.setA()函数调用bB.setB()函数,改变了aA.bB.str,同时也使bA.bB.str的值改变,而bA.str的值并没有因为aA.setA(),而改变,其还是jjf,而和aA.strjjf_1不同。这就是上面所说的情况。然而为什么会造成这种情况?

回顾一下文章的中间所说的:浅克隆的结果。。。
我们可以发现原来啊:实例aA与克隆实例bA中有自己的数据成员空间:str(String类型)bB(class B类型),这不很明显吗?他们都是引用类型,他们并不是String对象(jjf)本身和类(new B())本身,str的值只是"jjf"在堆中的地址,bB的值只是new B() 对象的地址。
所以虽然aAbA自有空间,不过他们自己数据成员的值指向的是同一堆空间:
所以:

  • 第一个例子中:改变自己数据成员的值当然不会相互影响,因为数据成员此时存放的是不是引用,而是实在的值
  • 第二个例子中:str存放的是引用,在改变str的值其实是让其指向其他空间,所以,改的是地址,不同两个实例中str的值不一样表示指向的是不同的地址,改地址的内容由字符串本身决定或者由bB对象本身决定。
  • 第三个例子中:aA.bBbA.bB都是引用变量,bA实例由aA实例克隆而来,所以aA.bB的值与bA.bB的值一样,即他们都指向同一堆空间,即这两个引用公用同一空间,setA(jjf_1, zxm_1)语句,jjf_1是用来改str的值的,而zxm_1是用来改bB.str的值的,所以,最终结果是aA.str指向的是jjf_1的字符串,而aA.bB.str指向的是zxm_1字符串,由于aA.bBbA.bB是一样的,所以bA.bB.str也指向zxm_1字符串,zxm则成为无引用指向的垃圾值!
    如果还没搞懂,可以回头再看。。。

要决解例题3的问题,实现深克隆:只需在在浅克隆的基础上加一条语句:

clone.bB = new B("jjf");
public A clone(){//重写克隆
		A clone = null;
		try	{
			clone = (A)super.clone(); 
		}catch(CloneNotSupportedException e){
			System.err.println(e.toString);
		}
		clone.bB = new B("jjf"); //深克隆
		return clone;
	}

即:深克隆是建立在浅克隆的基础上的。

至此:深浅克隆及堆地址的关系细述完毕


下面是主要的问题:
java对于所有数组都自定义了clone()函数,即,数组对象是自带clone()的,此外字符串,基本数据机基本数据类型一般不会有clone()函数,如果实现该函数,将十分多余的愚蠢,而实际上,数据复制,并非只能clone(),

你可能感兴趣的:(Java学习笔记,java职业遨游)