Java高级特性:clone()方法
源码
public class Objcet{
protected native Object clone() throws CloneNotSupportedException();
}
由源码可知。
- 第一:Objcet类的clone()方法是一个native方法。native方法的执行效率一般远高于Java中的非native方法(一般不是java语言所写)。这也解释了为什么要用Object的clone()方法,而不是先new一个类,然后把原始对象复制到新对象中,虽然这样也能实现clone功能。(JNI是Java Native Interface的 缩写。从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。)
- 第二:Object类中的clone()方法被protected修饰符修饰。(关于protected修饰符见我的其它文章。)这意味着clone()方法只对java.util.lang包可见,和继承了Object类的子类可见。当然所有类都是继承了Object类。
- 为什么要用protected修饰呢?
为了安全,安全性从两方面考虑。首先我们要clone一个Employee对象,应该只有Employee类能够克隆Employee对象。使用protected修饰符保证了其它不相关的类无法克隆Employee对象。其次是Object对要复制的对象一无所知,它只会逐域进行复制,如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题。但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样原对象和克隆的对象还会共享一些信息。所以默认的拷贝都是浅拷贝。要想在任何地方都使用clone方法,我们必须将它改为public类型,并且实现Cloneable接口。如果一开始就是public,我们就分不清到底是浅拷贝还是深拷贝了。使用protected和Cloneable就约束了类设计者要了解自己的克隆过程。
Cloneable接口是标记接口,它不包含任何需要实现的方法。如果一个对象请求克隆,但没有实现这个接口,就会产生一个受检查异常,因为clone方法默认实现中有使用instanceof进行接口判断。
这里给出代码
- 为什么要用protected修饰呢?
//cloneTest.java
package com.testbase.clone;
public class cloneTest {
public static void main(String[] args){
// 虽然我们已经实现了Cloneable接口,不会产生异常
// 但是编译器并不知道,会报错,所以这里要捕获异常
try
{
Employee tobin = new Employee(30000);
int salary = tobin.getSalary();
System.out.println(salary);
Employee shengsheng = tobin.clone();
int shengSalary = shengsheng.getSalary();
System.out.println(shengSalary);
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
}
}
//Employee.java
package com.testbase.clone;
public class Employee implements Cloneable
{
private int salary;
public Employee()
{
}
public Employee(int asalary)
{
salary = asalary;
}
@Override
public Employee clone() throws CloneNotSupportedException
{
Employee cloned = (Employee) super.clone();
return cloned;
}
public int getSalary() {
return salary;
}
}
- 第三:Object.clone()方法返回一个Object对象。我们必须进行强制转换才能得到我们需要的类型。(强制转换一定能成功吗?如果重写了clone方法一定成功。但是如果没有重写,不能转换。clone的是父类Object,无法向下造型,子类重写了clone方法,拷贝就是当前类的对象,暂时转为Object,还可以通过强制类型转换回来)
给出一段错误代码。在Employee.java中拷贝是因为在其它类中拷贝一定不成功。因为Object类clone方法的protected特性。只有继承了它的子类和java.util.lang包可以调用clone()方法。所以选择在子类Employee中测试。
package com.testbase.clone;
public class Employee implements Cloneable
{
private int salary;
public Employee()
{
}
public Employee(int asalary)
{
salary = asalary;
}
// @Override
// public Employee clone() throws CloneNotSupportedException
// {
// Employee cloned = (Employee) super.clone();
// return cloned;
// }
public int getSalary() {
return salary;
}
public static void main(String[] args){
try
{
Employee tobin = new Employee(30000);
int salary = tobin.getSalary();
System.out.println(salary);
Object shengsheng = tobin.clone(); //这里不能强制类型转换
int shengSalary = shengsheng.getSalary();//这里报错,因为Object没有getSalary方法
System.out.println(shengSalary);
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
}
}
深拷贝和浅拷贝
浅拷贝:拷贝引用,但是不拷贝引用指向的对象,对拷贝引用的对象进行修改,两份拷贝都会被修改。如果源对象和浅拷贝对象所共享的子对象都是不可变的,那么这种共享就是安全的。比如String类。或者在对象的生命周期内,子对象一直包含着不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况同样是安全的。
深拷贝:可能会更改的子对象也进行了拷贝。要进行深拷贝,需要一层层地重写clone方法。
- 数组是深拷贝。所有数组类型都有一个public的clone()方法,可以用这个方法建立一个新数组。
int[] a = {1,2,3,4};
int[] cloned = a.clone();
cloned[0]=11;
对象串行化实现拷贝
常见面试题
1.为什么进行拷贝
- 因为我们某个对象实例现在需要保存一些有效值,我们希望生成一个和原来一样的对象,对这个对象的修改不改变原对象的属性。
2.深拷贝和浅拷贝的区别
- 浅拷贝对基本数据类型生成一份新的拷贝,对引用类型,只是复制了引用,引用所指向的那块内存空间并没有拷贝
- 深拷贝对引用指向的那块内存空间也拷贝了一份(新内存空间)。换言之深拷贝要把复制的对象所引用的对象也都拷贝了一遍。
3.String克隆的特殊性在那里?StringBuffer和StringBuilder呢?
- 对基本数据类型都能自动实现深拷贝。而对引用类型是浅拷贝。String是引用类型的特例。因为String是不允许修改的。所以相当于进行了深拷贝,是安全的。由于String是不可变类,对String类中的很多修改操作都是通过new对象复制处理的。所以当我们修改clone前后对象里面String属性的值时,实际上就指向了新的内存空间。自然对clone前的源对象没有影响,类似于深拷贝。虽然它是引用类型,但是不影响我们深拷贝的使用。
而对于StringBuffer和StringBuilder,需要主动进行clone重写。否则就是浅拷贝。
4.实现对象克隆的常见方式有哪些,具体怎么做?
常用的方式有三种。
- 通过自己写一个clone方法,new一个同样的对象,赋值实现深度克隆,繁琐容易出错。
- 通过实现Cloneable接口并重写Object类的clone方法,分为深浅两种方式。
- 通过Serializable接口并用对象的序列化和反序列化来实现真正的深拷贝。
代码,主要是第2和第3个方法。
通过实现 Cloneable 接口并重写 Object 类的 clone() 方法实现浅克隆做法:Object 类中 clone 方法的默认实现最终是一个 native 方法(如果 clone 类没有实现 Cloneable 接口并调用了 Object 的 clone 方法就会抛出 CloneNotSupportedException 异常,因为 clone 方法默认实现中有使用 instanceof 进行接口判断),相对来说效率高些,默认实现是先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容,对基本数据类型就是值复制,而对非基本类型变量保存的仅仅是对象的引用,所以会导致 clone 后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
浅拷贝。
class Employee implements Clonealbe{
public Employee clone() throws CloneNotSupportedException
{
return (Employee) super.clone();
}
}
深拷贝
class Employee implements Clonealbe{
public Employee clone() throws CloneNotSupportedException
{
Employee cloned = (Employee) super.clone();
cloned.hireDay = (Date) hireDay.clone();
return cloned();
}
}
通过Serializable接口并用对象的序列化和反序列化来实现真正的深拷贝。(还未学到)
class CloneUtil {
public static <T extends Serializable> T clone(T obj) {
T cloneObj = null;
try {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
objOut.writeObject(obj);
objOut.close();
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream objIn = new ObjectInputStream(byteIn);
cloneObj = (T) objIn.readObject();
objIn.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return cloneObj;
}
}
class InfoBean implements Serializable {
public String name;
public int age;
}
class PeopleBean implements Serializable {
public String vipId;
public InfoBean infoBean;
public Object clone() {
return CloneUtil.clone(this);
}
}
参考文章:
https://www.cnblogs.com/gw811/archive/2012/10/07/2712252.html
https://blog.csdn.net/qq_26857649/article/details/84316081