Java中利用Serializable和Externalizable序列化与反序列化的使用

 
 
 
      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调用时需要将对象序列化之后才能调用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 对象的序列化是将对象的状态转换为字节流,以便将其存储在文件或通过网络进行传输。而反序列化则是将字节流重新转换为对象,以便在程序重新使用。 对象的序列化主要涉及到两个接口,即 SerializableExternalizableSerializable 接口是 Java 标准序列化机制的简单版本,所有需要序列化的类都需要实现这个接口。而 Externalizable 接口则需要自己实现序列化反序列化的方法。 在进行对象序列化时,可以使用 ObjectOutputStream 类来实现。通过这个类的 writeObject() 方法,可以将对象写入到输出流。而在进行反序列化时,可以使用 ObjectInputStream 类来实现。通过这个类的 readObject() 方法,可以将字节流重新转换为对象。 对象序列化的主要用途包括: 1. 对象的持久化:通过将对象序列化后存储在文件,可以实现对象的持久化,当程序再次启动时,可以反序列化读取文件并重新获取对象的状态。 2. 对象的传输:通过将对象序列化后通过网络传输,可以实现在不同计算机之间的对象传递。 在进行对象序列化时,需要注意以下几点: 1. 需要被序列化的对象和其引用的对象,都需要实现 Serializable 接口。 2. 对于不希望被序列化的属性,可以使用 transient 关键字进行标记。 3. 如果序列化的是一个对象的成员变量,而不是整个对象,那么成员变量对应的类也需要实现 Serializable 接口。 总之,Java 对象序列化反序列化是一种非常有用的机制,它可以将对象的状态转换为字节流进行存储或传输,以便在需要时重新获取对象。通过使用序列化机制,我们可以实现对象的持久化和传输,使得编程更加灵活和便捷。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值