• java的序列化和反序列化


    序列化和反序列化

    序列化:将对象写入IO流中;
    反序列化:从IO流中恢复对象;

    序列化机制允许将实现序列化的Java对象转换为字节序列,并将字节序列保存在磁盘中,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使地对象可以脱离程序的运行而独立存在。
    序列化的反序列化的目的是为了对象可以脱离程序单独的进行传输,或者持久化,在I/O相关开发中具有重要意义

    初步使用Java实现序列化和反序列

    class Person1 implements Serializable {
        private String name;
        private String age;
    
        public Person1() {
            System.out.println("调用Person的无参构造函数");
        }
    
        public Person1(String name, String age) {
            this.name = name;
            this.age = age;
            System.out.println("调用Person的有参构造函数");
        }
    
        @Override
        public String toString() {
            // TODO 自动生成的方法存根
            return "Person{'name' :" + name + ",'age' :" + age + "}";
        }
    }
    
    
    
    // 去掉Person类实现的序列化接口
    public class Teacher implements Serializable {
        private String name;
        private Person1 person;
    
        public Teacher(String name, Person1 person) {
            this.name = name;
            this.person = person;
        }
    
        public static void main(String[] args) throws Exception {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Teacher.txt"));
            Person1 P = new Person1();
            Teacher t = new Teacher("mom", P);
            oos.writeObject(t);
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Teacher.txt"));
            Teacher tc = (Teacher) ois.readObject();
            System.out.println(tc.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    a) 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化
    b) 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
    c) 如果想让某个变量不被序列化,使用transient修饰。
    d) 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
    e) 反序列化时必须有序列化对象的class文件。
    f) 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
    g) 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
    i) 同一对象序列化多次(被写入同一个文件多次),只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。所以反序列化出来的对象都是同一个 如果被写到不同的文件,反序列化出来的对象将不是同一个
    j) 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。
    k) 数组不能显式地声明serialVersionUID,因为它们始终都有默认的计算值,但是对于数组类,无需匹配serialVersionUID。
    l) 可以通过序列化和反序列化的方式实现对象的深复制。

    单例模式反序列问题

    单例模式下,通过反序列化的方式创建对,每次创建的对象不是同一个对象,需要解决这个问题,需要重写readResolve方法,方法内部返回类中的单例对象,这样每次反序列化之后,都会找到类中的静态变量对象替换反序列的后的对象,注意是把序列化的对象丢弃了统一使用同一个对象

    class User implements Serializable {
        private static final User instance=new User();
        public String name;
        public int age;
    
        private User()
        {
            name="Sean";
            age=23;
        }
    
        public static User getInstance()
        {
            return instance;
        }
        private Object readResolve()
        {
            return instance;
        }
    
    }
    public class single {
        public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
            User a=User.getInstance();
    
            ObjectOutputStream os=new ObjectOutputStream(new FileOutputStream("user.txt"));
            os.writeObject(a);
            os.flush();        //利用管道将实例a序列化输出到文件user.txt中
            os.close();
    
            ObjectInputStream is=new ObjectInputStream(new FileInputStream("user.txt"));
            User b=(User) is.readObject();  //利用管道将user.txt中的实例反序列化输入进来作为对象b
            is.close();                     //这实际上就等于重新生成了另一个单例对象
    
            b.name="Moon";
            b.age=31;
    
            System.out.println("a's name:"+a.name+" "+"a's age:"+a.age);
            System.out.println("b's name:"+b.name+" "+"b's age:"+b.age);
            //输出对象a、b的信息,不同则证明反序列化破解了单例模式
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    补充:反射模式下的单例问题

    单例模式的类下,通过反射创建的对象,两次创建的对象不是同一个。解决办法没有版本解决反射下的单例攻击,只能通过枚举类型来禁用反射
    可以通过对类对象加锁的方式观察类对象反射的时候被调用的次数,来发现攻击,立马报错。

    class SimpleSingleton {
        private static SimpleSingleton singleton;
        private static int count;
        private SimpleSingleton(){
            synchronized (SimpleSingleton.class) {
                if (count>0) {
                    throw new RuntimeException("error:创建了两个实例!");
                }
                ++count;
            }
        }
    
        public static SimpleSingleton getInstance() {
            if (singleton == null) {
                singleton = new SimpleSingleton();
            }
            return singleton;
        }
    
        public void say() {
            System.out.println("j");
        }
    }
    
    public class test{
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
            Constructor<SimpleSingleton> declaredConstructor = SimpleSingleton.class.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            // 反射获取count变量
            Field countField = SimpleSingleton.class.getDeclaredField("count");
            countField.setAccessible(true);
    
            // new了一次构造方法,count=1
            SimpleSingleton instance = SimpleSingleton.getInstance();
            // 重新设置count的值为0
            //countField.set(instance, 0);
    
            // newInstance底层原理也是调用构造方法来进行对象的实例化的
            // count=1
            SimpleSingleton simpleSingleton = declaredConstructor.newInstance();
            simpleSingleton.say();
            System.out.println(instance == simpleSingleton);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    serialVersionUID 版本号

    serialVersionUID 需要显示指定,默认的指定是不可以的。

    • 通过显示指定serialVersionUID ,那么不同端相同的类,虽然结构发生了变化,但是仍然可以正常解析,
    • 如果不想的UID就无法解析了
      在这里插入图片描述

    对象的深浅复制

    浅复制(复制引用但不复制引用的对象)深复制(复制对象和其引用对象)

    通过clone方法实现深浅拷贝

    clone实现拷贝,对象需要实现clonable接口,并且重写clone方法,如果内部存在引用类型,引用类型也需要实现clonable接口,并且重写clone方法。这种方法很复杂,需要保证所有的引用类型都复制到了父类object对象上

    class Address implements Cloneable {
        private String type;
        private String value;
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    class Personr implements Cloneable {
        private String name;
        private Integer age;
        private Address address;
    //    @Override
    //    protected Object clone() throws CloneNotSupportedException {
    //        return super.clone();
    //    }
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Object obj=super.clone();
    
            Address a=((Personr)obj).getAddress();
            ((Personr)obj).setAddress((Address) a.clone());
            
            return obj;
        }
    
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    }
    
    
    public class Tclone {
    
        public static void main(String[] args) throws CloneNotSupportedException {
            Personr p1=new Personr();
            p1.setAge(31);
            p1.setName("Peter");
    
            Personr p2=(Personr) p1.clone();
    
            System.out.println(p1==p2);//false
            p2.setName("Jacky");
            System.out.println("p1="+p1);//p1=Person [name=Peter, age=31]
            System.out.println("p2="+p2);//p2=Person [name=Jacky, age=31]
    
        }
    
        @Test
        public void testShallowCopy() throws Exception{
            Address address=new Address();
            address.setType("Home");
            address.setValue("北京");
    
            Personr p1=new Personr();
            p1.setAge(31);
            p1.setName("Peter");
            p1.setAddress(address);
    
            Personr p2=(Personr) p1.clone();
            System.out.println(p1==p2);//false
    
            p2.getAddress().setType("Office");
            System.out.println("p1="+p1);
            System.out.println("p2="+p2);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    通过序列反序列直接深拷贝

    反序列和序列化天然支持深拷贝

    其他方法

    深拷贝方法总结

  • 相关阅读:
    LaTeX 公式与绘图技巧
    python+appium自动化测试如何控制App的启动和退出
    如何提高LaTeX的编译速度?
    Vue 全局事件总线$bus
    npm install 下载不下来依赖解决方案
    linux 安装下载conda并创建虚拟环境
    Redis(事务和持久化)(很重要!)
    Spring源码之BeanFactory 与 ApplicationContext 的区别
    基于安卓的校园信息助手系统设计(Eclipse开发)
    sed命令在Mac和Linux下的不同
  • 原文地址:https://blog.csdn.net/qq_37771209/article/details/126436974