1、Serializable序列化时不会调用默认的构造器,而Externalizable序列化时会调用默认构造器的,Serializable不需要自己实现序列化方法可以使用默认的序列化方法,而Externalizable需要自己实现序列化!!!
2、Serializable:一个对象想要被序列化,那么它的类就要实现 此接口,这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。
3.如是要一个类是可序列化的,那么它的子类也是可序列化的。
Externalizable:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性。序列化就是将对象存进文件,反序列化就是将对象从文件中读取出来。
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
注意:
对象的序列化并不属于新的Reader和Writer层次结构的一部分,而是沿用老式的InputStream和OutputStream结构,在某些情况下,不得不混合使用两种类型的层次结构。
恢复了一个反序列化的对象后,如果想对其做更多的事情(对象.getClass().xxx),必须保证JVM能在本地类路径或者因特网的其他什么地方找到相关的.class文件。
恢复对象的默认构建器必须是public的,否则会抛异常。
由于Externalizable对象默认时不保存对象的任何字段,所以transient关键字只能伴随Serializable使用,虽然Externalizable对象中使用transient关键字也不报错,但不起任何作用
方法writeObject处理对象的序列化。如果声明该方法,它将会被ObjectOutputStream调用而不是默认的序列化进程。如果你是第一次看见它,你会很惊奇尽管它们被外部类调用但事实上这是两个private的方法。并且它们既不存在于java.lang.Object,也没有在Serializable中声明。那么ObjectOutputStream如何使用它们的呢?这个吗,ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod(通过getDeclareMethod可以获得私有方法),所以这些方法不得不被声明为private以至于供ObjectOutputStream来使用。
调用了defaultWriteObject()和defaultReadObject()。它们做的是默认的序列化进程,就像写/读所有的non-transient和 non-static字段(但他们不会去做serialVersionUID的检查).通常说来,所有我们想要自己处理的字段都应该声明为transient。这样的话,defaultWriteObject/defaultReadObject便可以专注于其余字段,而我们则可为这些特定的字段
serialVersionUID的作用:
序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。
在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。
具体的序列化过程是这样的:序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。
有两种生成方式:
一个是默认的1L,比如:private static final long serialVersionUID = 1L;
一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
private static final long serialVersionUID = xxxxL; 例如当你的类Serialized存到硬盘上面后,可是后来你却更改了类别的field(增加或减少或改名),当你Deserialize时,就会出现Exception的,这样就会造成不兼容性的问题。但当serialVersionUID相同时,它就会将不一样的field以type的预设值Deserialize,可避开不兼容性问题。
案例一:
public class SkillMode implements Externalizable {
private static final long serialVersionUID = 1L;
private Logger logger = LoggerFactory.getLogger(SkillMode.class);
private Map<Integer,DmDialogDefinition> allSkill;
private String version;
private String versionMessage;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(version);
out.writeObject(versionMessage);
out.writeObject(allSkill); //其他对象都是按照这样的格式读进去
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
version = (String) in.readObject();
versionMessage = (String) in.readObject();
allSkill = (Map<Integer, DmDialogDefinition>) in.readObject();//其他对象都是按照这样的格式读出来
}
public Map<Integer, DmDialogDefinition> getAllSkill() {
return allSkill;
}
public void setAllSkill(Map<Integer, DmDialogDefinition> allSkill) {
this.allSkill = allSkill;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getVersionMessage() {
return versionMessage;
}
public void setVersionMessage(String versionMessage) {
this.versionMessage = versionMessage;
}
public void load(File file) throws IOException { // 从文件中读出至类中
logger.debug("File.load path:{}",file.getAbsolutePath());
ObjectInputStream ios = null;
InputStream in = null;
try {
in = new FileInputStream(file);
ios = new ObjectInputStream(new BufferedInputStream(in));
in = null;
this.readExternal(ios);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new IOException(e);
} finally {
saveClose(ios);
saveClose(in);
}
}
public void save(File file) throws IOException {
ObjectOutputStream oos = null;
OutputStream out = null;
try {
out = new FileOutputStream(file);
oos = new ObjectOutputStream(new BufferedOutputStream(out));
out = null;
this.writeExternal(oos); // 保存对象到文件
} finally {
saveClose(oos);
saveClose(out);
}
}
private void saveClose(AutoCloseable c){
if (c != null){
try {
c.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
用法:
1.从文件中读取出来:
SkillModel skillModel = new SkillModel();
File file=new File("path") //path为存取的文件路径
if(file.exists()){
skillModel.load(File);//将从文件中读取出来的值传给定义的skillModel实体
}
skillModel.save(file);//将skillModel实体存到文件中
案例二:
public class Person implements Serializable {
private static final long serialVersionUID = 123456789L;
public int id;
public String name;
public transient String age; //不会被序列化
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public String toString() {
return "Person: " + id + " " + name;
}
}
序列化功能:
public class SerialTest {
public static void main(String[] args) throws IOException {
Person person = new Person(1234, "wang");
System.out.println("Person Serial" + person);
FileOutputStream fos = new FileOutputStream("Person.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(person);
oos.flush();
oos.close();
}
}
反序列化功能:
public class DeserialTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person;
//在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,String的值是"" ,对象型的是 null
FileInputStream fis = new FileInputStream("Person.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
person = (Person) ois.readObject();
ois.close();
System.out.println("Person Deserial" + person);
}
}
一个序列化的类要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就需要有默认的无参的构造函数。在父类没有实现 Serializable接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。
如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。
根据父类对象序列化的规则,我们可以将不需要被序列化的字段抽取出来放到父类中,子类实现 Serializable 接口,父类不实现,根据父类序列化规则去父类无参构造方法中初始化。
用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。实现Externalizable接口的类必须要提供一个无参的构造器,且访问权限为 public。
dubbo利用RPC调用时需要将对象序列化之后才能调用