Java 序列化Serializable 及 transient 关键字解读

java 中的 Serializable 接口是用于实现对象 序列化和反序列化 的功能。那么什么是序列化和反序列化呢?

  • 序列化: 就是将对象的当前状态写入字节流,用于保存或传输的过程
  • 反序列化:则是从字节流中恢复对象,是序列化的逆过程

通过序列化,能够将当前 java 对象保存至文件,或通过网络传输,并在需要时通过反序列化进行恢复。

序列化和反序列化

只有实现了 Serializable 接口的类才能通过序列化功能进行保存和恢复。下面进行示例说明:

  • 下面定义了一个名为 UserInfo 的类,并实现 Serializable 接口
public class UserInfo implements Serializable {
    private static final long serialVersionUID = -7701101573245777694L;
    private String username;
    private String password;
    
    public UserInfo(String username, String password) {
        this.username = username;
        this.password = password;
    }
  
    @Override
    public String toString() {
        return "UserInfo [ username=" + username + ", password=" + password + "]";
    }
}
  • 通过 ObjectOutputStream 进行序列化输出
public class SerializeDemo {
    public static void main(String[] args) {
        UserInfo userinfo = new UserInfo("eric", "eric_123456");
        
        try (FileOutputStream fos = new FileOutputStream(new File("demo.txt")); 
                ObjectOutputStream oos =  new ObjectOutputStream(fos)) {
             oos.writeObject(userinfo);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我们看下demo.txt文件夹内容

��sr*com.eric.learning.java._serialize.UserInfo� 0脎{肉��Lpasswordt�Ljava/lang/String;Lusernameq~�xpt�eric_123456t�eric

序列化后的文件无法直接查看,但是文件中可以看到上文设置的成员变量值 eric 和 eric_123456

  • 我们再通过 ObjectInputStream 进行反序列化恢复:
public class SerializeDemo {
    public static void main(String[] args) {
        UserInfo userinfo = null;
        
        try (FileInputStream fis = new FileInputStream(new File("demo.txt"));
                ObjectInputStream ois = new ObjectInputStream(fis)) {
            userinfo = (UserInfo) ois.readObject();  
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        
        System.out.println(userinfo);
    }
}

输出结果为:

UserInfo [username=eric, password=eric_123456]

serialVersionUID

在示例中,我们在实现 Serializable 接口时,生成了一个变量 serialVersionUID ,这个变量是干嘛用的呢?

实际上这个变量是可序列化类的一个版本标识。 在序列化对象时,对象的 serialVersionUID 会被一同写入到字节流,而反序列化时 JVM 会将字节流中读取的 serialVersionUID 同类的 serialVersionUID 比较,如果不相同则会反序列化失败。

如果实现了 Serializable 接口,但不指定 serialVersionUID 的值,那么编译器在编译时会自动生成一个 serialVersionUID.

示例:

  • 假设我们增加了 Userinfo 中的字段:
public class UserInfo implements Serializable {
    private static final long serialVersionUID = -7701101573245777694L;
    
    private Long   id; 
    private String username;
    private String password;
}

更新后,我们希望旧 UserInfo 对象的序列化结果,无法再反序列化到新 UserInfo 类对象,这时我们可以重新生成 serialVersionUID 让旧对象的序列化结果失效。

public class UserInfo implements Serializable {
    // before
    // private static final long serialVersionUID = -7701101573245777694L; 
  
    // now
    private static final long serialVersionUID = -7701101573245777694L     
  
    private Long   id; 
    private String username;
    private String password;
}

这时如果反序列化旧 UserInfo 对象,将抛以下错误:

java.io.InvalidClassException: com.eric.learning.java._serialize.UserInfo; local class incompatible: stream classdesc serialVersionUID = -7701101573245777694, local class serialVersionUID = -7701101573245777695
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1829)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)

transient

在 UserInfo 类中,有个用于存储用户密码的 password 字段,密码属于私密信息,我们不希望其被随意泄露。这时我们可以使用 transient 关键字。

用 transient 修饰的字段,在序列化时将被忽略:

public class UserInfo implements Serializable {
    private static final long serialVersionUID = -7701101573245777694L;
    private String username;
    private transient String password;   // transient 修饰
    
    public UserInfo(String username, String password) {
        this.username = username;
        this.password = password;
    }
}

再次序列化输出到文件:

public class SerializeDemo {
    public static void main(String[] args) {
        UserInfo userinfo = new UserInfo("eric", "eric_123456");
        
        try (FileOutputStream fos = new FileOutputStream(new File("demo.txt")); 
                ObjectOutputStream oos =  new ObjectOutputStream(fos)) {
             oos.writeObject(userinfo);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

文件内容中已经没有 password 内容:

��sr*com.eric.learning.java._serialize.UserInfo� 0脎{肉��Lusernamet�Ljava/lang/String;xpt�eric

你可能感兴趣的:(Java 序列化Serializable 及 transient 关键字解读)