深入学习java源码之Object.clone()与Object.notifyAll()
super.clone()
最早使用clone方法的是object类,这个类是所有类的父类,在重写方法时调用父类方法肯定用super了,this是本身,但子类本身没有clone方法,是从父类object继承的。
Object中的clone执行的时候使用了RTTI(run-time type identification)的机制,动态得找到目前正在调用clone方法的那个reference,根据它的大小申请内存空间,然后进行bitwise的复制,将该对象的内存空间完全复制到新的空间中去,从而达到shallowcopy的目的。
所以调用super.clone() 得到的是当前调用类的副本,而不是父类的副本。
要让实例调用clone方法就需要让此类实现Cloneable接口,API里面还有句话是:如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常,这便是“合法”的含义。 但请注意,Cloneable接口只是个标签接口,不含任何需要实现的方法,就像Serializable接口一样。
总之,一般如果子类没有特殊需要而重写clone()方法就直接用super.clone() 就行了。
浅拷贝与深拷贝
浅拷贝,就是Object默认的clone方法,完全的copy了这个类,基本数据类型copy了值,引用数据类型copy的是对象的引用,所以如果要对对象进行修改,可以使用深拷贝。 所谓的深拷贝,就是自己重写了一下clone方法,将引用变量变成值传递而不是引用传递。
浅克隆(shadow clone)
克隆就是复制一个对象的复本.若只需要复制对象的字段值(对于基本数据类型,如:int,long,float等,则复制值;对于复合数据类型仅复制该字段值,如数组变量则复制地址,对于对象变量则复制对象的reference。
class Person implements Cloneable{
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
String name;
int age;
Job job;
@Override
public String toString(){
return "name: " + name + ",age: " + age + ",job: " + job;
}
}
class Job{
String jobName;
String address;
}
public class CloneTest {
@Test
public void shaowClone() throws Exception{
Person p1 = new Person();
p1.setName("guo");
p1.setAge(22);
Job job = new Job();
job.setJobName("IT");
job.setAddress("shanghai");
p1.setJob(job);
Person p2 = p1.clone();
System.out.println(p1.toString());
System.out.println(p2.toString());
p2.getJob().setJobName("programmer");
System.out.println(p1.getJob().getJobName());
}
得到结果:
name: guo,age: 22,job: test.Job@4f2410ac
name: guo,age: 22,job: test.Job@4f2410ac
programmer
clone方法就是返回一个原对象的拷贝,默认走的是浅拷贝。克隆的目的是复制对象,但是新的对象是独立于原来的对象的,一般我们克隆出来的对象都在一些属性做了更改,这个时候需要小心一点,如果更改的属性是引用数据类型,可能会影响到原来的对象,如果都是基本数据类型则不怕。使用clone方法的前提是继承Cloneable接口,数组默认实现了Cloneable接口,默认走的是浅拷贝。
public class ShadowClone implements Cloneable{
private int a; // 基本类型
private int[] b; // 非基本类型
// 重写Object.clone()方法,并把protected改为public
@Override
public Object clone(){
ShadowClone sc = null;
try
{
sc = (ShadowClone) super.clone();
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return sc;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}
}
ShadowClone c1 = new ShadowClone();
//对c1赋值
c1.setA(100) ;
c1.setB(new int[]{1000}) ;
System.out.println("克隆前c1: a="+c1.getA()+" b="+c1.getB()[0]);
//克隆出对象c2,并对c2的属性A,B,C进行修改
ShadowClone c2 = (ShadowClone) c1.clone();
//对c2进行修改
c2.setA(50) ;
int []a = c2.getB() ;
a[0]=5 ;
c2.setB(a);
System.out.println("克隆前c1: a="+c1.getA()+" b="+c1.getB()[0]);
System.out.println("克隆后c2: a="+c2.getA()+ " b[0]="+c2.getB()[0]);
克隆前c1: a=100 b=1000
克隆前c1: a=100 b=5
克隆后c2: a=50 b[0]=5
基本类型可以使用浅克隆,而对于引用类型,由于引用的是内容相同,所以改变c2实例对象中的属性就会影响到c1。所以引用类型需要使用深克隆。另外,在开发一个不可变类的时候,如果这个不可变类中成员有引用类型,则就需要通过深克隆来达到不可变的目的。
深克隆(deep clone)
深克隆与浅克隆的区别在于对复合数据类型的复制。若对象中的某个字段为复合类型,在克隆对象的时候,需要为该字段重新创建一个对象。
就是自己重写了一下clone方法,将引用变量变成值传递而不是引用传递
public class DeepClone implements Cloneable {
private int a; // 基本类型
private int[] b; // 非基本类型
// 重写Object.clone()方法,并把protected改为public
@Override
public Object clone(){
DeepClone sc = null;
try
{
sc = (DeepClone) super.clone();
int[] t = sc.getB();
int[] b1 = new int[t.length];
for (int i = 0; i < b1.length; i++) {
b1[i] = t[i];
}
sc.setB(b1);
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return sc;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}
}
Class Object
是类Object
结构的根。 每个班都有Object
作为超类。 所有对象(包括数组)都实现了这个类的方法。
Modifier and Type | Method and Description |
---|---|
protected Object |
clone() 创建并返回此对象的副本。 |
boolean |
equals(Object obj) 指示一些其他对象是否等于此。 |
protected void |
finalize() 当垃圾收集确定不再有对该对象的引用时,垃圾收集器在对象上调用该对象。 |
类> |
getClass() 返回此 |
int |
hashCode() 返回对象的哈希码值。 |
void |
notify() 唤醒正在等待对象监视器的单个线程。 |
void |
notifyAll() 唤醒正在等待对象监视器的所有线程。 |
String |
toString() 返回对象的字符串表示形式。 |
void |
wait() 导致当前线程等待,直到另一个线程调用该对象的 |
void |
wait(long timeout) 导致当前线程等待,直到另一个线程调用 |
void |
wait(long timeout, int nanos) 导致当前线程等待,直到另一个线程调用该对象的 |
java源码
package java.lang;
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
public final native Class> getClass();
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }
}