package job;
/**
* ps:java中的clone()方法的功用类似于C++中的拷贝构造函数
* 拷贝对象还可以用对象流中转一下获得,需要实现标记型接口Serializable
* Serializable接口中的方法对程序是不可见的,因此实现了该接口的类不需要实现
* 额外的方法,当把一个序列化的对象写入ObjuctOutputStream时,JVM就会实现
* Serializable接口中的方法,将一定格式的文本-对象的序列化信息,写入OBjuceOutputStream
* 输出流的目的地
* 使用对象流把一个对象写入文件时不仅需要保证该对象是序列化的,而且该对象的
* 成员对象也必须是序列化的
*/
import java.io.Serializable;
import java.util.Date;
/**
* Cloneable是标记型接口(另一个常见的就是Serializable),声明了该接口表明可用clone()方法
* 如果不声明Cloneable接口使用clone()方法就会报CloneNotSupportException
* 按惯例要重写protected clone()方法为public的,但是clone()是Object的protected方法
* 已有一个field to field的浅拷贝实现,因此不像其它接口的抽象方法那样必须重写
* 但是因为Object中的clone()方法的访问权限是protected的,这就意味着,如果一个对象想使用
* 该方法得到自己的一个复制品,就必须保证自己的类与Object类在同一个包中,这显然是不可能的,
* 因为Java不允许用户编写的类拥有java.lang这样的包名(尽管可以编译拥有java.lang包名的类,
* 但运行时JVM拒绝加载这样的类),为了能让一个对象使用clone()方法,创建该对象的类需要重写(覆盖)
* clone()方法,并且讲访问权限提升为public权限,为了能使用被覆盖的clone()方法,只需在重写的clone()
* 方法中使用关键字super调用Object类的clone()方法即可。
* 再者对象中有非基本类型的域且其内容有可能会发生改变时,必须重写clone()方法为深拷贝
* @author shijin
*
*/
public class TestClone implements Cloneable{
private Date date;
private DeepCopy d;
public Date getT() {
return date;
}
public DeepCopy getD() {
return d;
}
public void setT(Date date) {
this.date = date;
}
public void setD(DeepCopy d) {
this.d = d;
}
@Override
public boolean equals(Object obj) {
if(obj == null)
return false;
else {
if(obj instanceof TestClone) {
TestClone t = (TestClone)obj;
if(this.date.equals(t.date) && this.d.equals(t.d))
return true;
}
return false;
}
}
/**
* 其实clone()的默认实现类似于函数函数中对date域的处理:field to field,浅拷贝,shallow copy
* 对于域中存在的非初始类型且其内容有可能发生变化的情况
* 就要改写clone()方法实现深拷贝deep copy,如函数中对d域的处理
* 不然拷贝对象与原对象不独立,两个对象的非初始类型域会共享内存空间
* 一个对象内存内容的改变会造成两个对象同步改变
*
* 这里改变对象的内容与改变对象的引用不是一个概念
* field to field类型的复制只会造成两个相等的对象
* 对两个相等的对象,即为两个对象引用指向同一块内存空间
* 一个对象引用指向的改变不会影响另一个对象
* 只不过改变指向的对象指向了另一块内存,原指向断开被新的指向替代
* 未改变的对象依然指向原内存,两个对象不再相等
* 但是对象内容的改变会影响另一个对象,因为修改的是同一块内存
* 对此处的理解可以参考String变量的不可变性
* String类型的变量作为变量可以相继指向不同字符串常量
* 但对于一个固定的指向,其内存空间里的内容是不能改变的
* String s = new String("a");s ="a" + "b";两句话的内存分析
* s变量在栈内存
* new出来的String对象在堆内存
* "a"、"b"、"ab"三个字符串常量在数据段(常量池)
* s定义时指向堆内存中的String对象"a"
* 第二句执行后指向数据段中的字符串常量"ab"
* 内存中的内容不变,只是字符串变量的指向变了
*
*/
@Override
public Object clone() throws CloneNotSupportedException {
// TestClone t = (TestClone)super.clone();
// t.setT(this.getT());
// t.setD(this.getD());//浅拷贝
// t.setD(new DeepCopy(this.d.getX()));//深拷贝
// 或者如下,如果DeepCopy实现了Cloneable接口的话,这里就有点递归的意思了
// t.setD((DeepCopy)this.d.clone());
// return t;
return super.clone();
// return new TestClone();
}
/**
* @param args
*/
public static void main(String[] args) {
TestClone t = new TestClone();
t.setT(new Date());
t.setD(new DeepCopy(1));
TestClone tClone = null;
try {
tClone = (TestClone)t.clone();
} catch (CloneNotSupportedException e1) {
e1.printStackTrace();
}
// 这个!=是必须的,定义要求拷贝对象与原对象独立,不然就失去拷贝的意义了
System.out.println("t " + ((t != tClone)?"!=":"==") + " t.clone()");
// clone()中对d进行深拷贝时t.d != t.clone().d
System.out.println("t.d " + ((t.d != tClone.d)?"!=":"==") + " t.clone().d");
// clone()中对t进行浅拷贝t.date == t.clone().date
System.out.println("t.date " + ((t.date != tClone.date)?"!=":"==") + " t.clone().date");
// 这个==虽然不是必须的,但是只要类及其父类clone()方法中的拷贝对象都是通过super.clone()获得
// t.getClass() == tClone.getClass()就成立
System.out.println("t.getClass() " + ((t.getClass() == tClone.getClass())?"==":"!=") + " tClone.getClass()");
System.out.println("\tt = " + t);
System.out.println("\ttClone = " + tClone);
// 即使clone()采用默认实现,仍然返回true,自动识别类型
// Object中的clone执行的时候使用了RTTI(run-time type identification)的机制,即多态的实现机制
// 动态得找到目前正在调用clone方法的那个reference,根据它的大小申请内存空间
// 然后进行bitwise的复制,将该对象的内存空间完全复制到新的空间中去,从而达到shallow copy的目的。
// 所以调用super.clone() 得到的是当前调用类的副本,而不是父类的副本。
System.out.println("\ttClone 是不是TestClone的实例:" + (tClone instanceof TestClone));
System.out.println("\tt.getClass() = " + t.getClass());
System.out.println("\ttClone.getClass() = " + tClone.getClass());
// 这个==也不是必须的,比如在clone()和equals()都改写时
// 若对象中有一个域为unique,则该对象的克隆就可能与该对象不相等
System.out.println("t " + ((t.equals(tClone))?"equales":"doesn't eaual") + " t.clone");
// 本段代码验证浅拷贝与深拷贝的区别
System.out.println("t.d.x改动前:" + t.d);
System.out.println("tClone.d.x改动前:" + tClone.d);
t.d.setX(2);
System.out.println("t.d.x改动后:" + t.d);
System.out.println("tClone.d.x改动前:" + tClone.d);
// 本段代码参考上面验证改变引用与改变内容的区别
// 对date的实验中直接更换date域的引用,而不是改变date的内容,所以体现不出浅拷贝的弊端
// 后期date域指向外部引用后两个对象也独立了
System.out.println(t.date);
System.out.println(tClone.date);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tClone.setT(new Date());
System.out.println(t.date);
System.out.println(tClone.date);
}
}
/**
* 验证深拷贝的辅助类
* @author shijin
*
*/
class DeepCopy implements Cloneable{
int x;
public DeepCopy(int x) {
this.x = x;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
@Override
public String toString() {
return x+"";
}
@Override
protected Object clone() throws CloneNotSupportedException {
//成员为基本类型,不用改写方法
return super.clone();
}
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof DeepCopy) {
DeepCopy d = (DeepCopy)obj;
if(d.x == this.x)
return true;
}
return false;
}
}
结合实例详解clone()函数,Cloneable接口以及深拷贝与浅拷贝的问题
最新推荐文章于 2022-10-15 08:28:00 发布