由于指针的存在不仅会给开发人员带来不便,同时也是造成程序不稳定的根源之一,为了消除C/C++语言的这些缺点,Java语言取消了指针的概念,但这只是在Java语言中没有明确提供指针的概念与用法,而实质上每个new语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如何去操作这个指针而已。
package basic;
class Obj{
private String str="default value";
public void setStr(String str) {
this.str=str;
}
public String toString() {
return str;
}
}
public class clone {
private Obj aObj=new Obj();
private int aInt=0;
public Obj getAObj() {
return aObj;
}
public int getAInt() {
return aInt;
}
public void changeObj(Obj inObj) {
inObj.setStr("changed value");
}
public void changeInt(int inInt) {
inInt=1;
}
public static void main(String[] args) {
clone oRef=new clone();
System.out.println("********引用类型********");
System.out.println("调用changeObj()前:"+oRef.getAObj());
oRef.changeObj(oRef.getAObj());
System.out.println("调用changeObj()后:"+oRef.getAObj());
System.out.println("********基本数据类型********");
System.out.println("调用changeInt()前:"+oRef.getAInt());
oRef.changeInt(oRef.getAInt());
System.out.println("调用changeInt()后:"+oRef.getAInt());
}
}
输出结果:
引用类型
调用changeObj()前:default value
调用changeObj()后:changed value
基本数据类型
调用changeInt()前:0
调用changeInt()后:0
上面两个类似的方法却有着不同的运行结果,主要原因时Java在处理基本数据类型的时候,都是采用按值传递(传递的是输入参数的拷贝)的方式,除此之外的其他类型都是按引用传递(传递的是对象的一个引用)的方式执行。对象除了在函数调用的时候是引用传毒,在使用“=”赋值的时候也采用引用传递。
package basic;
class Obj2{
private int aInt=0;
public int getAInt() {
return aInt;
}
public void setAInt(int int1) {
aInt=int1;
}
public void changeInt() {
this.aInt=1;
}
}
public class clone2 {
public static void main(String[] args) {
Obj2 a=new Obj2();
Obj2 b=a;
b.changeInt();
System.out.println("a:"+a.getAInt());
System.out.println("b:"+b.getAInt());
}
}
输出结果:
a:1
b:1
在实际的编程中,经常会遇到从某个已有的对象A创建出另一个与A具有相同状态的对象B,并且对B的修改不会影响到A的状态,例如Prototype(原型)模式中,就需要复制(clone)一个对象实例。在Java语言中,仅通过简单的赋值操作显然无法达到这个目的,而Java提供了一个简单且有效的clone方法来满足这个需求。
Java中所有的类默认继承自Object类,而Object类中提供了一个clone方法。这个方法的作用是返回一个Object对象的拷贝,这个拷贝函数返回的是一个新的对象而不是一个引用。那么怎么样使用这个方法呢?以下是使用clone方法的步骤:
1)实现clone的类首先需要继承Cloneable接口。Cloneable接口实质上是一个标识接口,没有任何借口方法。
2)在类中重写Object类中的clone方法。
3)在clone方法中调用super.clone()。无论clone类的继承结构是什么,super.clone()都会直接或间接调用java.lang.Object类的clone()方法。
4)把浅拷贝的引用指向原型对象新的克隆体。
package basic;
class Obj3 implements Cloneable{
private int aInt=0;
public int getAInt() {
return aInt;
}
public void setAInt(int int1) {
aInt=int1;
}
public void changeInt() {
this.aInt=1;
}
public Object clone() {
Object o=null;
try {
o=(Obj3)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
public class clone3 {
public static void main(String[] args) {
Obj3 a=new Obj3();
Obj3 b=(Obj3)a.clone();
b.changeInt();
System.out.println("a:"+a.getAInt());
System.out.println("b:"+b.getAInt());
}
}
运行结果:
a:0
b:1
在C++语言中,当开发人员自定义拷贝构造函数的时候,会存在深拷贝与浅拷贝之分。Java在重载clone方法的时候也存在同样的问题,当类中只有一些基本的数据类型的时候,采用上述方法就可以了,但是当类中包含了一些对象的时候,就需要深拷贝了,实现方法是对对象调用clone方法完成深拷贝后,接着对对象中的非基本类型的属性也调用clone方法完成深拷贝。
package basic;
import java.util.*;
class Obj4 implements Cloneable{
private Date birth=new Date();
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth=birth;
}
public void changeDate() {
this.birth.setMonth(4);
}
public Object clone() {
Obj4 o=null;
try {
o=(Obj4)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
//实现深拷贝
o.birth=(Date)this.getBirth().clone();
return o;
}
}
public class clone4 {
public static void main(String[] args) {
Obj4 a=new Obj4();
Obj4 b=(Obj4)a.clone();
b.changeDate();
System.out.println("a="+a.getBirth());
System.out.println("b="+b.getBirth());
}
}
运行结果:
a=Thu Dec 02 15:21:05 CST 2021
b=Sun May 02 15:21:05 CST 2021
那么在编程的时候如何选择使用哪种拷贝方式呢?
首先,检查类有无非基本类型(即对象)的数据成员。如果没有,则返回super.clone()即可,如果有,则要确保类中包含的所有非基本类型的成员变量都实现了深拷贝。
Object o=super.clone();//先执行浅拷贝
对于每一个对象attr:
o.attr=this.getAttr().clone();
最后返回o
引申:
(1)浅拷贝和深拷贝的区别
浅拷贝:被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用任然指向原来的对象。换言之,浅拷贝仅仅赋值所考虑的对象,而不是复制它所引用的对象。
深拷贝:被复制对象的所有变量都含有与原来对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把复制的对象所引用的对象都复制了一遍。
(2)clone()方法的保护机制
clone()方法的保护机制在Object中是被声明为protected的。以User类为例,通过声明为protected,就可以保证只有User类里面才能“克隆”User对象,原理可以参考public、protected、private的讲解。