特点
一个类实现了Cloneable接口 指向@link java.lang.Object#clone()} 方法是合法的 使得一个 field-for-field copy的类的实例的拷贝
在不实现Cloneable接口的对象上调用Object的clone方法 会导致CloneNotSupportedException异常抛出。
按照约定,实现此接口的类应当重写Object.clone方法 (被保护)带有public
注意 此接口不包含clone方法 因此不可能仅仅依赖实现了这个接口就拷贝一个对象 。 即使这个clone方法 被反射调用 也不能保证成功
java.lang.Object#clone()
创建并返回此对象的副本。复制 可能取决于对象的类。总体的意图是 对应任何对象x 表达式
代码语言:javascript复制 x.clone() != x
会是true 并且表达
代码语言:javascript复制x.clone().getClass() == x.getClass()
将会是true 但是这些不是绝对要求 通常情况下:
代码语言:javascript复制 x.clone().equals(x)
将是true,这不是绝对的要求
按照惯例,返回的对象应该通过调用获得{@code super.clone}. 如果一个class 和 它所有的父类 除了 {@code Object})遵循这个惯例, 它应当是这样的
代码语言:javascript复制 x.clone().getClass() == x.getClass()
按照惯例,该方法返回的对象应该是独立的。这个对象(正在被克隆) 为了实现这种独立性,可能需要修改返回的对象的一个或多个字段 在返回之前使用{@code super.clone} 通常,这意味着复制包含内部 深层结构的任何可变对象 正在克隆的对象 和替换这些对象的应用 利用 引用的副本。
如果一个类只包含原始字段或者不可变对象的引用 通常意味着 通过{@code super.clone}返回的对象没有fileds字段需要被修改
{@code clone} 方法给Object对象类 操作一个特别的克隆操作。 首先,如果这个对象的类不实现Cloneable接口 CloneNotSupportedException将会抛出,所有的数组将会被考虑实现接口Cloneable。一个数组类型T[] clone方法的返回的类型是T[] T是一个引用或者基本类型 否则,此方法将创建此类的新实例,然后初始化所有的字段 内容完全是对象的对应字段,如果通过赋值,字段的内容不会拷贝他们自己。因此 这种方法执行对象的“shallow copy”,而不是deep copy
对象的类本身不实现Cloneable接口 所以调用clone方法 在一个对象 它的类是 对象 将抛出一个运行时异常。
浅拷贝和深度拷贝
为什么要克隆? 克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的
Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。而通过clone方法赋值的对象跟原来的对象时同时独立存在的
ShallowClone 在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
- 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
- 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
示例
代码语言:javascript复制package abc;
class Address implements Cloneable {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); //浅复制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address)addr.clone(); //深度复制
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" stu1.getNumber() ",地址:" stu1.getAddr().getAdd());
System.out.println("学生2:" stu2.getNumber() ",地址:" stu2.getAddr().getAdd());
addr.setAdd("西湖区");
System.out.println("学生1:" stu1.getNumber() ",地址:" stu1.getAddr().getAdd());
System.out.println("学生2:" stu2.getNumber() ",地址:" stu2.getAddr().getAdd());
}
}
deep copy
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
(如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。)
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
代码语言:javascript复制public class Outer implements Serializable{
private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID
public Inner inner;
//Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化]
public Outer myclone() {
Outer outer = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
}
代码语言:javascript复制public class Inner implements Serializable{
private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
public String name = "";
public Inner(String name) {
this.name = name;
}
@Override
public String toString() {
return "Inner的name值为:" name;
}
}
参考资料: https://www.cnblogs.com/Qian123/p/5710533.html#_label2
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/181879.html原文链接:https://javaforall.cn