序列化和反序列化
序列化:将对象写入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就无法解析了
对象的深浅复制
浅复制(复制引用但不复制引用的对象)深复制(复制对象和其引用对象)
通过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);
}
}
通过序列反序列直接深拷贝
反序列和序列化天然支持深拷贝
其他方法