序列化和反序列化

序列化:将对象写入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());
    }
}

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的信息,不同则证明反序列化破解了单例模式
    }
}

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

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

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);

    }
}

serialVersionUID 版本号

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

  • 通过显示指定serialVersionUID ,那么不同端相同的类,虽然结构发生了变化,但是仍然可以正常解析,
  • 如果不想的UID就无法解析了
  • 获取电脑序列号 java实现_java


对象的深浅复制

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

通过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);
    }
}

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

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

其他方法