在学习集合的途中,遇到了序列化的问题,就是
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
这个transient关键字。没了解过。
1.序列化的定义
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。
2.如何使用
在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。此处将创建一个可序列化的类Person,一个枚举类Gender。
Gender:
public enum Gender { MALE,FEMALE }
Person:
public class Person implements Serializable { private String name = null; private Integer age = null; private Gender gender = null; transient private String like = null; public Person(String name, Integer age, Gender gender,String like) { System.out.println("arg constructor"); this.name = name; this.age = age; this.gender = gender; this.like = like; } public Person() { System.out.println("non-arg constructor"); } 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 Gender getGender() { return gender; } public void setGender(Gender gender) { this.gender = gender; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", gender=" + gender + "," + like + "]"; } }
Gender枚举类,表示性别。
Person类,实现了Serializable接口,它包含四个字段:name,String类型;age,Integer类型;gender,Gender类型;like,String类型。另外,还重写该类的toString()方法,以方便打印Person实例中的内容。而like是被transient修饰。
这里写一个序列化程序:
public class SimpleSerial { public static void main(String[] args) throws IOException, ClassNotFoundException { File file = new File("Person.out"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); Person p = new Person("tony",23,Gender.MALE,"play game"); oos.writeObject(p); oos.close(); System.out.println("=============================="); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Object newP = ois.readObject();//没有强制转换到Person类 ois.close(); System.out.println(newP); } }
运行后输出为:
arg constructor ============================== Person [name=tony, age=23, gender=MALE,null]
1.此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。
2.被transient修饰的变量,没有被序列化。
3.为什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,使用ObjectOutputStream来持久化对象,在该类中有如下代码:
// remaining cases if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } }
从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
3.serialVersionUID
如果你的类序列化存到硬盘上面后,可是后来你却更改了类的field(增加或减少或改名),当你Deserialize时,就会出现Exception的,这样就会造成不兼容性的问题。
但当serialVersionUID相同时,它就会将不一样的field以type的预设值Deserialize,可避开不兼容性问题。
在Eclipse里写代码时,开始如果没加这个serialVersionUID就有警告提示。Eclipse提供两种生成方式:
一种是默认的1L,比如:private static final long serialVersionUID = 1L;
一种是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long serialVersionUID = xxxxL;
参照博客https://blog.csdn.net/mashangyou/article/details/21833357这个深入的较多,有时间再看看。