transient,中文翻译是短暂的,和对象序列化、反序列化有关。
一个类只要实现了Serializable接口,则该类实例就可以序列化,具体来说实例的每个非静态成员变量都会序列化。注意是非静态成员变量,静态成员变量不会序列化。但是假如某些字段涉及敏感信息,不能序列化存储到文件中或者经网络传输,则就应该用transient修饰。用transient修饰的变量在对象序列化时默认不会序列化,同时反序列化得到的对象中该变量值会是对应类型的默认值。
代码示例:
Address类:
public class Address implements Serializable { private String nation; private String city; private String buildingNo; public String getNation() { return nation; } public void setNation(String nation) { this.nation = nation; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getBuildingNo() { return buildingNo; } public void setBuildingNo(String buildingNo) { this.buildingNo = buildingNo; } public Address() { } public Address(String nation, String city, String buildingNo) { this.nation = nation; this.city = city; this.buildingNo = buildingNo; } @Override public String toString() { return "Address{" + "nation='" + nation + '\'' + ", city='" + city + '\'' + ", buildingNo='" + buildingNo + '\'' + '}'; } }
Person类:
public class Person implements Serializable { private String name; private Integer age; private Address address; 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 Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public Person(String name, Integer age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}'; } }
测试类Test:
public class Test { public static void main(String[] args) { String path = "d:/s.txt"; try (OutputStream output = new FileOutputStream(path); ObjectOutputStream oos = new ObjectOutputStream(output); InputStream input = new FileInputStream(path); ObjectInputStream ois = new ObjectInputStream(input) ) { oos.writeObject(new Person("zhangsan", 18, new Address("China", "Shenzhen", "FuTian"))); Person person = (Person) ois.readObject(); System.out.println(person); } catch (Exception e) { e.printStackTrace(); } } }
注意,上面说的是用transient修饰的变量在对象序列化时默认不会序列化,同时反序列化得到的对象中该变量值会是对应类型的默认值。注意,是默认,不是一定。因为是有办法让transient修饰的变量也参与序列化的。只需在类中加入两个方法即可:
void writeObject(ObjectOutputStream oos) throws IOException;
void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException;
其中第一个是序列化方法,可以指定序列化的成员变量,第二个是反序列化方法,可以指定反序列化的成员变量。可以在这两个方法中显式指定被transient修饰的变量,这样该变量同样可以参与序列化和反序列化。
代码示例:
其他类代码不变,Person类:
public class Person implements Serializable { private String name; private Integer age; private transient Address address; 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 Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public Person(String name, Integer age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}'; } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeObject(address); System.out.println("person serialized"); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); address = (Address) ois.readObject(); System.out.println("person deserialized"); } }
上面代码中,标红的oos.defaultWriteObject(); 和 ois.defaultReadObject(); 是调用默认的序列化方法和反序列化方法去操作没有transient修饰的非静态成员变量(default方法能且仅能操作non-static、non-transient变量),之后用oos.writeObject(address); 和 address = (Address) ois.readObject(); 来显式指定address变量。这样,执行完Test类的main方法之后,同样可以打印出adderss信息,即使address用transient修饰了。
Serializable接口还有个Externalizable子接口。该接口有2个方法:
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
如果自定义类实现了Externalizable接口,则transient在该类中不起任何作用,属性能不能序列化和有没有transient修饰没有半毛钱关系。如果想序列化该类实例的某个属性,必须在writeExternal()方法中显式write,如果想反序列化时能够获取到属性值,必须在readExternal()显式read,且各属性write和read的顺序必须一样。如果writeExternal()方法和readExternal()方法中没有具体实现,则任何属性都不会序列化。
代码示例:
其他类不变,Person类:
public class Person implements Externalizable { private String name; private Integer age; private transient Address address; 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 Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public Person() { } public Person(String name, Integer age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}'; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeObject(age); out.writeObject(address); System.out.println("person serialized"); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); age = (Integer) in.readObject(); address = (Address) in.readObject(); System.out.println("person deserialized"); } }
需要指出的是,假如想要通过反序列化来得到Externalizable实例,则Externalizable接口实现类必须要有无参构造器,否则会报java.io.InvalidClassException异常,因为在反序列化过程中,是先通过无参构造器创造出对象,然后调用各属性的setter给属性设置值的。
同时需要指出的是,假如一个类没有实现Serializable接口或者Externalizable接口,则在序列化该类实例时会报java.io.NotSerializableException异常。更进一步,假如该类没有被transient修饰的非静态成员变量所属类型没有实现Serializable接口或者Externalizable接口,则在序列化时也会报java.io.NotSerializableException异常。