Java transient关键字使用

371 阅读3分钟

关键字transient的作用

我们知道一个对象只要实现了Serializable接口,那么它就可以序列化。我们不必关心序列化的过程,只要这个类实现了Serializable接口,那么这个类的所有属性和方法都会自动序列化。

但是,在真实项目开发中,我们可能会遇到这样的问题:用户的敏感信息(如银行卡、密码等),我们可能不希望它在网络中传输。那么我们就可以使用transient关键在来修饰这些属性。那么,这些被transient修饰的属性就不会被序列化。

我们来验证一下:

public class User implements Serializable {
	
    private String username;
    private transient String passwd;
    
    //省略getter/setter方法
}
public class TransientTest {
	public static void main(String[] args) {
    	User user = new User();
        user.setUsername("zhangsan");
        user.setPasswd("123456");
        System.out.println("read before serializable:");
        System.out.println("username:" + user.getUsername());
        System.out.println("password:" + user.getPasswd());
        
        try {
        	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/user.txt"));
            oos.writeObject(user);
            oos.flush();
            oos.close();
        } catch (FileNotFoundException e) {
        	e.printStackTrace();
        } catch (IOException e) {
        	e.printStackTrace();
        }
        
        try {
        	ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/user.txt"));
            user = (User)ois.readObject();
            ois.close();
            
            System.out.println("\nread after serializable:");
            System.out.println("username:" + user.getUsername());
            System.out.println("password:" + user.getPasswd());
        } catch (FileNotFoundException e) {
        	e.printStackTrace();
        } catch (IOException e) {
        	e.printStackTrace();
        } catch (ClassNotFoundException e) {
        	e.printStackTrace();
        }
    }
}

输出结果如下:

read before Serializable: 
username: zhangsan
password: 123456

read after Serializable: 
username: zhangsan
password: null

由此说明,被transient修饰的passwd属性没有被序列化。

transient使用小结

  1. 被transient修饰的属性无法被序列化
  2. transient关键字只能修饰属性,不能修饰类和方法。
  3. 如果变量类型是用户自定义类型,那么就要实现Serializable接口,否则不能序列化。
  4. static类型的变量无论是否被transient关键字修饰,都不会被序列化。

第4点,可能会有很多人怀疑。在为passwd属性增加了static关键字之后,发现还是能够获取到值的。如下:

import java.io.Serializable;

public class User implements Serializable {

    private String username;
    private static String passwd;

	//省略getter和getter方法
}

运行结果如下:

read before serializable:
username:zhangsan
password:123456

read after serializable:
username:zhangsan
password:123456

其实这个passwd属性的值不是从反序列化中得到的,而是JVM中对应静态变量的值。不信的话,我们来验证一下:

public class TransientTest {
    public static void main(String[] args) {
        User user = new User();
        user.setUsername("zhangsan");
        user.setPasswd("123456");
        System.out.println("read before serializable:");
        System.out.println("username:" + user.getUsername());
        System.out.println("password:" + user.getPasswd());

        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/user.txt"));
            oos.writeObject(user);
            oos.flush();
            oos.close();
            
            user.setPasswd("changePassword"); //在序列化后修改密码
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/user.txt"));
            user = (User)ois.readObject();
            ois.close();

            System.out.println("\nread after serializable:");
            System.out.println("username:" + user.getUsername());
            System.out.println("password:" + user.getPasswd());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

read before serializable:
username:zhangsan
password:123456

read after serializable:
username:zhangsan
password:changePassword

被transient修饰的变量真的不能被序列化吗?

public class ExternalizableTest implements Externalizable {

    private transient String content = "是的,我将会被序列化,不管我是否被transient修饰";

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(content);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        content = (String) in.readObject();
    }

    public static void main(String[] args) {
        ExternalizableTest et = new ExternalizableTest();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/content.txt"));
            oos.writeObject(et);
            oos.flush();
            oos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/content.txt"));
            et = (ExternalizableTest) ois.readObject();
            ois.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        System.out.println(et.content);
    }
}

输出结果如下:

是的,我将会被序列化,不管我是否被transient修饰

这是为什么呢?不是说被transient修饰的变量不能被序列化

我们知道在java中,对象的序列化可以通过实现两种接口进行实现。若实现的是Serializable接口,那么所有的序列化会自动进行。若实现的是Externalizable接口,则没有任何东西自动序列化,需要在writeExternal方法中手动指定所要序列化的变量。

这就是为什么上面这个例子,content可以被序列化的原因。