【当年笔记】克隆和序列化
1.浅克隆
需要克隆的对象必须实现 Cloneable 接口,并重写 clone() 方法,即可实现对此对象的克隆。 只会复制对象的值类型,而不会复制对象的引用类型。
2.深克隆
深克隆就是复制整个对象信息,包含值类型和引用类型。
2.1深克隆实现方式
- 序列化实现深克隆:先将原对象序列化到内存的字节流中,再从字节流中反序列化出刚刚存储的对象,这个新对象和原对象就不存在任何地址上的共享,这样就实现了深克隆。
- 所有引用类型都实现克隆:要复制对象的所有引用类型都要实现克隆,所有对象都是复制的新对象,从而实现了深克隆。
深克隆实现方式一:序列化
实现思路:先将要拷贝对象写入到内存中的字节流中,然后再从这个字节流中读出刚刚存储的信息,作为一个新对象返回,那么这个新对象和原对象就不存在任何地址上的共享,自然实现了深拷贝。 参考代码:
class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
BirdChild birdChild = new BirdChild();
birdChild.name = "小小鸟";
Bird bird = new Bird();
bird.name = "小鸟";
bird.birdChild = birdChild;
// 使用序列化克隆对象
Bird bird2 = CloneUtils.clone(bird);
bird2.name = "黄雀";
bird2.birdChild.name = "小黄雀";
System.out.println("bird name:" + bird.name);
System.out.println("bird child name:" + bird.birdChild.name);
System.out.println("bird name 2:" + bird2.name);
System.out.println("bird child name 2:" + bird2.birdChild.name);
}
}
class CloneUtils {
public static <T extends Serializable> T clone(T obj) {
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bo);
oos.writeObject(obj);
oos.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流
ObjectInputStream oi = new ObjectInputStream(bi);
//返回生成的新对象
cloneObj = (T) oi.readObject();
oi.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
深克隆实现方式二:所有引用类型都实现克隆
class CloneableTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ParrotChild parrotChild = new ParrotChild();
parrotChild.name = "小鹦鹉";
Parrot parrot = new Parrot();
parrot.name = "大鹦鹉";
parrot.parrotChild = parrotChild;
// 克隆
Parrot parrot2 = (Parrot) parrot.clone();
parrot2.name = "老鹦鹉";
parrot2.parrotChild.name = "少鹦鹉";
System.out.println("parrot name:" + parrot.name);
System.out.println("parrot child name:" + parrot.parrotChild.name);
System.out.println("parrot name 2:" + parrot2.name);
System.out.println("parrot child name 2:" + parrot2.parrotChild.name);
}
}
class Parrot implements Cloneable {
public String name;
public ParrotChild parrotChild;
@Override
protected Object clone() throws CloneNotSupportedException {
Parrot bird = (Parrot) super.clone();
bird.parrotChild = (ParrotChild) parrotChild.clone();
return bird;
}
}
class ParrotChild implements Cloneable {
public String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2.2 clone有什么好处,为什么要重写clone方法。
clone好处:
- 使用方便:假如要复制一个对象,但这个对象中的部分属性已经被修改过了,如果不使用克隆的话,需要给属性手动赋值,相比克隆而已麻烦很多;
- 性能高:查看 clone 方法可以知道,它是 native 方法,native 方法是原生函数,使用操作系统底层的语言实现的,因此执行效率更高;
- 隔离性:克隆可以确保对象操作时相互隔离
重写clone是因为 Object 中的 clone() 方法被声明为 protected 访问级别,所以非 java.lang 包下的其他类是不能直接使用的。因此要想实现克隆功能,就必须实现 Cloneable,并重写 clone() 方法才行。
3.序列化和反序列化
将对象转换成二进制流的过程叫做序列化,把二进制流恢复为数据对象的过程就称之为反序列化
3.1序列化和反序列参考代码
先把对象序列化到磁盘,再从磁盘中反序列化出对象:
class SerializableTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 对象赋值
User user = new User();
user.setName("老王");
user.setAge(30);
System.out.println(user);
// 创建输出流(序列化内容到磁盘)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.out"));
// 序列化对象
oos.writeObject(user);
oos.flush();
oos.close();
// 创建输入流(从磁盘反序列化)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.out"));
// 反序列化
User user2 = (User) ois.readObject();
ois.close();
System.out.println(user2);
}
}
class User implements Serializable {
private static final long serialVersionUID = 3831264392873197003L;
private String name;
private int age;
@Override
public String toString() {
return "{name:" + name + ",age:" + age + "}";
}
// setter/getter...
public 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;
}
}
3.2serialVersionUID 的作用是什么?
定义了 serialVersionUID 值之后,可以使序列化和反序列化向后兼容。也就是说如果 serialVersionUID 的值相同,修改对象的字段(删除或增加),程序不会报错,之后给没有的字段赋值为 null,而如果没有指定 serialVersionUID 的值,如果修改对象的字段,程序就会报错。