首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >升级Java Serializable类

升级Java Serializable类
EN

Stack Overflow用户
提问于 2011-02-15 23:06:59
回答 3查看 4.1K关注 0票数 8

我读过很多关于序列化和serialVersionUID使用的博客。他们中的大多数都提到使用它来维护可序列化类的状态。

我有这样的场景:

我知道旧的serialVersionUID和新的serialVersionUID。

在用旧的serialVersionUID读入一个对象时,我想要操纵数据,使其适合新版本,只有当我读入的对象是旧类型时,我才想费心这么做。

这看起来应该是非常直截了当的!

有没有办法在读入对象时获得serialVersionUID?

在调用序列化类中的readObject方法之前,会抛出InvalidClassException,因此我无法在那里访问它。

我发现的唯一提示是覆盖ObjectInputStream,以便readClassDescriptor()可用,尽管这似乎是一个常见问题的重量级解决方案!

我们非常感谢您的帮助!

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2011-02-15 23:52:28

我将介绍几种可能的方法来支持序列化中的类/接口的旧版本:

Use Migrators

在这种情况下,您的Java项目中需要以下内容:

迁移程序接口是一个不可变的接口,它统一了所有这些类,并将该接口的旧实现注释为@Deprecated

  • The
  1. 迁移程序类,它可以帮助您将弃用的对象转换为新的

让我从一开始就说,使用旧版本的对象并不总是可行的(请参阅versioning of serializable objects上的Oracle文档)。为简单起见,我们假设在升级时,您的类始终实现您定义的接口IEntity

代码语言:javascript
复制
public interface IEntity extends Serializable {
   // your method definitions
}

假设您最初使用的是这个类:

代码语言:javascript
复制
public class Entity implements IEntity {
   private static final long serialVersionUID = 123456789L;
   // fields and methods
}

如果您需要使用新的serialVersionUID将类Entity升级为新的实现,那么首先将其注释为@Deprecated,但不要重命名它,也不要将其移动到另一个包中:

代码语言:javascript
复制
@Deprecated 
public class Entity implements IEntity {
private static final long serialVersionUID = 123456789L;
  // fields and methods
}

现在使用一个新的serialVersionUID (重要)和一个额外的构造函数创建新的实现,如下所示……

代码语言:javascript
复制
public class Entity_new implements IEntity {
  private static final long serialVersionUID = 5555558L;

  public Entity_new(IEntity){
     // Create a new instance of Entity_new copying the given IEntity
  }

}

当然,整个过程中最关键的部分是如何实现上面的构造函数。如果您已经将旧类型Entity的一些对象序列化为二进制文件(例如使用ObjectOutputStream),并且现在已经迁移到Entity_new,则可以将它们解析为Entity的实例,然后将它们转换为Entity_new的实例。下面是一个示例:

代码语言:javascript
复制
public class Migrator {

   private final IEntity entity;
   private Class<? extends IEntity> newestClass = Entity_new.class;

   public Migrator(final IEntity entity){
    this.entity = entity;
   }

   public Migrator setNewestClass(Class<? extends IEntity> clazz){
     this.newestClass = clazz;
     return this;
   }

   public IEntity migrate() throws Exception {
     Constructor<? extends IEntity> constr =  
        newestClass.getConstructor(IEntity.class);
     return constr.newInstance(this.entity);
   }
}

当然,还有其他不需要特定构造函数或使用java reflection的替代方法。还有很多其他的设计方法可供选择。还要注意,为了简单起见,上面的代码完全省略了异常处理和对null对象的检查。

设计一个通用的可序列化接口

如果适用,您可以首先尝试为您的类设计一个将来不太可能更改的接口。如果您的类需要存储一组很可能被修改的属性,请考虑为此使用Map<String, Object>,其中String指的是属性名/标识符,Object指的是相应值。

自定义readObject和writeObject

还有另一种方法可以提供对旧版本的支持,为了完整性,我会提到这一点,但我不会选择这种方法。您可以实现private void readObject(ObjectInputStream in)private void writeObject(ObjectOutputStream out),使其既能容纳当前版本的类,也能容纳所有以前版本的类/接口。我不确定这样的事情是否总是可行和可持续的,你可能最终会有一个非常混乱和冗长的方法实现。

替代序列化技术

这没有回答操作员的问题,但我认为值得提出来。您可以考虑将对象序列化为某些ASCII格式,如JSON、YAML或XML。在这种情况下,除非您强烈地重新设计可序列化接口,否则可扩展性将开箱即用。如果您正在寻找可扩展的二进制协议,BSON (binary JSON)是一个很好的选择。也许这是为您的对象提供跨软件的可移植性的最佳方法,这些软件可能不是用Java实现的。

票数 12
EN

Stack Overflow用户

发布于 2011-02-15 23:32:49

您应该保持相同的serialVersionUID。序列化的字段不必与类本身的字段匹配。使用ObjectInputStream.readFields并定义一个serialPersistentFields (尽管一定要确保拼写正确)。

票数 6
EN

Stack Overflow用户

发布于 2011-02-15 23:33:53

这不是一件容易的事情。如果您的代码可以同时支持这两种类类型,那么处理这种情况的最佳方法就是不更改serialVersionUID,而是检测数据是新的还是旧的,并相应地读取数据。

如果您想一次性地将旧数据升级到新数据,您需要设置某种类型的类,其中旧类和新类都可用于进程(例如,单独的类加载器)。您需要使用旧的类读取数据,复制到新的类,然后重写。然而,这绝对不是做事情的最佳方式。

简而言之,更改serialVersionUID不是维护状态的方式,而是指示不兼容性的方式(即,在这种情况下,跳出是唯一的解决方案)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5005330

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档