【当年笔记】克隆和序列化

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 的值,如果修改对象的字段,程序就会报错。

posted @ 2022-06-08 17:19  正文儿  阅读(79)  评论(0编辑  收藏  举报