引言
Java包含基本数据类型和引用数据类型两大数据类型。对于基本数据类型的拷贝或者说克隆可以声明一个新的变量直接进行赋值,而引用数据类型却不行。因为在Java中任何一个对象变量的值都是对存储在另一个地方的一个对象的引用, 并没有实际包含这个对象。 这说明如果还是使用赋值的方式进行拷贝,那任何一个对象变量改变都会影响另一个变量,并没有达到克隆的效果。
那如果想要copy一个新对象,但初始状态和原对象一样,也就是克隆对象,应该怎么做呢?幸运的是,Object类提供了一个protected native Object clone() throws CloneNotSupportedException;
方法,让我们调用看看。先创建一个Person类,
public class Person {
private String name;
private int age;
private Date birthDay; public Person() {} public Person(String name, int age, Date birthDay) { this.name = name; this.age = age; this.birthDay = birthDay; } protected String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirthDay() { return birthDay; } public void setBirthDay(Date birthDay) { this.birthDay = birthDay; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", birthDay=" + birthDay + '}'; } } 复制代码
然后调用
what?protected修饰符不是对本包和所有子类可见吗?Object不是每个类的父类吗?
重识protected
其实是我们一直理解错了。protected
是访问控制修饰符之一,声明protected
的成员变量或者方法将仅对本包和子类可见。
主要要知道:B是A的子类,A有一个protected的方法fun,B中代码可以访问A。但是如果有一个与父类不同包的C类,在其中有B的对象变量b,b.one()就不对了,即使C也继承A。
明白就跳过例子,到下个标题
举个例子,王家村(java.lang包)里有一个老王(Object类),他会徒手生钱(clone()方法),但只帮本村人和孩子生钱(protected修饰本包和子类可见)。有两个儿子小王(Person类)和测试王(测试类CloneTest类)都去张家村(现在Person和CloneTest所处的自己创建的包)打工,有天测试王没钱了,混得差自己不敢找爸爸要钱,于是叫小王去要(CloneTest类创建Person去调用Clone()),我小王自己也出来混得不咋地凭啥帮你要啊,自己找爸拿去,不同意(编译错误)!
总结:
-
无论在哪,子类内部能自己调用受保护的父类方法。
-
自己找爸爸要钱肯定会给。
-
-
与父类不同包的子类创建同样不同包的子类来调用受保护的父类方法是错误的。
-
出来混得差的别让同样出来混得差的兄弟帮忙找父亲要钱,没得脸。
-
-
与父类不同包的子类创建与父类同包的子类来调用受保护的父类方法是错误的。
-
出来混得差的用 打电话的方式让家里的兄弟帮忙找父亲要钱,还是开不了口的。
-
-
与父类同包的子类创建与父类不同包的子类来调用受保护的父类方法是正确的。
-
在村里的兄弟让你 亲自回村里要钱,当着面还是可以的。
-
-
都与父类同包的子类,与父类同包其他类一样,可创建任何子类去调用,无论子类是否在同包。
-
相亲相爱一村人,不分你我。
-
总之,只有在村里的才能让爸爸徒手生钱,否则自己开口(只有本包去创建父子类调用父类受保护方法才是合法的,否则就只有子类在类中调用)。
切回正题
所以根据上面总结的第二点,应该知道为什么会报错了吧,也知道只有在同包中才能这样调用。但我们不可能将代码写在java.lang包,于是只剩下第二种方法,复写Object的clone方法,也就是自己学习徒手生钱,因为我们是可以在本类中使用父类受保护的方法。
实现步骤:
-
实现Cloneable接口
如果不实现接口会报CloneNotSupportedException异常,应该是因为Object的clone方法是navtive本地方法的原因吧。Cloneable接口是记号接口,里面并没有接口方法,只是标记是个可以克隆的类。
-
重新定义clone方法,并指定为public修饰符
复写的修饰符只能权限更大或一样,一样是protected那就没意思了。。。。
public class Person implements Cloneable{
// 省略...
@Override
public Object clone() throws CloneNotSupportedException {
return (Person)super.clone(); } } 复制代码
这里我们直接调用了Object的clone方法。Object类对这个对象一无所知,只能逐个域拷贝,对于基本类型没有问题,但是如果实例域里还有引用类型呢?它还是会复制的地址,那会发生什么呢?我们给克隆的人的生日日期时间戳变为1,即1970。
public static void main(String[] args) {
Person person = new Person("Money", 22, new Date());
try {
Person clonePerson = (Person)person.clone();
clonePerson.setName("jack");
clonePerson.setAge(11); // 给克隆的人的生日日期时间戳变为1,即1970 clonePerson.getBirthDay().setTime(1); System.out.println(clonePerson); System.out.println(person); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } 复制代码
结果两个都变二月了,因为它改变了Date对象的值了,而两人指向的是同一个Date对象:
状况如下:
这种情况我们称之为浅拷贝,
深拷贝
如果想要一个全新的克隆,我们就要在重新定义的时候把引用类型的实例域也进行拷贝,称为深拷贝
修改后的新clone方法:
@Override
public Object clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
// 多克隆一份Date对象给克隆人引用
person.birthDay = (Date)birthDay.clone();
return person; } 复制代码
运行上面的main函数:
发现本人没有跟着改变了!
这时候你应该发现为什么一直没有说到String,因为他是一个不可变对象,不可变保证了String变量保存的地址值不会改变,并且没有方法改变其字符串内容,都是会重新new对象。那这样一来只有set方法,改变的是克隆人birthday的引用地址,并不会影响到本人的birthday,每个实例独自一份变量并不会相互影响。
而之所以使用Date举例是因为它本身已经实现了Cloneable接口,并且它有setTime()能改变Date对象的值。
使用深拷贝:
如果有错,不要打我,偷偷告诉我,谢谢。