ああ、自分はJavaが一番得意ですが、Javaの基本的な知識をあまり知らないことが多く、例えばtransientキーワードは以前使ったことがなかったので、その役割がわかりませんでした。今日筆記試験の問題を解いていたら、これに関する問題があることに気づいたので、時間をかけてtransientキーワードの使い方を整理して、知識を少し増やそうと思います~~~さて、余計な話はここまでにして、以下始めます。

1. transient の作用と使用方法

私たちは皆知っているように、オブジェクトがSerilizableインターフェースを実装している限り、そのオブジェクトはシリアライズできます。Javaのこのシリアライズ方式は開発者に多くの便利さを提供します。シリアライズの具体的なプロセスを気にする必要はなく、このクラスがSerilizableインターフェースを実装しているだけで、このクラスのすべての属性とメソッドは自動的にシリアライズされます。

しかし、実際の開発プロセスでは、このクラスの一部の属性はシリアライズする必要があるが、他の属性はシリアライズする必要がないという問題によく遭遇します。例えば、ユーザーにパスワードや銀行カード番号などの機密情報がある場合、安全のために、ネットワーク操作(主にシリアライズ操作を含み、ローカルシリアライズキャッシュにも適用)で送信されたくありません。これらの情報に対応する変数にtransientキーワードを付けることができます。言い換えれば、このフィールドのライフサイクルは呼び出し側のメモリ内にのみ存在し、ディスクに書き込まれて永続化されることはありません。

要するに、Javaのtransientキーワードは私たちに便利さを提供します。Serilizableインターフェースを実装し、シリアライズする必要のない属性の前にtransientキーワードを追加するだけで、オブジェクトをシリアライズするときに、この属性は指定した宛先にシリアライズされません。

例

import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; /** * @description transientキーワードを使用して変数をシリアライズしない * 読み取り時に、データを読み取る順序はデータを保存した順序と必ず一致させること * * @author Alexia * @date 2013-10-15 */ public class TransientTest { public static void main(String[] args) { User user = new User(); user.setUsername("Alexia"); user.setPasswd("123456"); System.out.println("read before Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); try { ObjectOutputStream os = new ObjectOutputStream( new FileOutputStream("C:/user.txt")); os.writeObject(user); //Userオブジェクトをファイルに書き込む os.flush(); os.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { ObjectInputStream is = new ObjectInputStream(new FileInputStream( "C:/user.txt")); user = (User) is.readObject(); //ストリームからUserデータを読み取る is.close(); System.out.println("\nread after Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class User implements Serializable { private static final long serialVersionUID = 8294180014912103005L; private String username; private transient String passwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } }

出力は:

read before Serializable: 
username: Alexia
password: 123456

read after Serializable: 
username: Alexia
password: null

パスワードフィールドはnullであり、デシリアライズ時にファイルから情報をまったく取得していないことを示しています。

2. transient 使用のまとめ

1)変数がtransientで修飾されると、その変数はオブジェクト永続化の一部ではなくなり、シリアライズ後はその変数の内容にアクセスできなくなります。

2)transientキーワードは変数のみを修飾でき、メソッドやクラスは修飾できません。注意すべき点として、ローカル変数はtransientキーワードで修飾できません。変数がユーザー定義クラスの変数である場合、そのクラスはSerializableインターフェースを実装する必要があります。

3)transientキーワードで修飾された変数はシリアライズできなくなります。静的変数は、transientで修飾されているかどうかに関わらず、シリアライズできません。

第三点については、混乱する人もいるかもしれません。なぜなら、Userクラスのusernameフィールドの前にstaticキーワードを追加しても、プログラムの実行結果は変わらず、static型のusernameも "Alexia" として読み出されるからです。これは第三点と矛盾しないのでしょうか?実際はこうです。第三点は確かに間違いありません(静的変数はtransientで修飾されているかどうかに関わらずシリアライズできません)。デシリアライズ後のクラス内のstatic型変数usernameの値は、現在のJVM内の対応するstatic変数の値です。この値はJVM内のものであり、デシリアライズで得られたものではありません。信じられませんか?では、以下で証明します。

例

import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; /** * @description transientキーワードを使用して変数をシリアライズしない * 読み取り時に、データを読み取る順序はデータを保存した順序と必ず一致させること * * @author Alexia * @date 2013-10-15 */ public class TransientTest { public static void main(String[] args) { User user = new User(); user.setUsername("Alexia"); user.setPasswd("123456"); System.out.println("read before Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); try { ObjectOutputStream os = new ObjectOutputStream( new FileOutputStream("C:/user.txt")); os.writeObject(user); //Userオブジェクトをファイルに書き込む os.flush(); os.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { //デシリアライズの前にusernameの値を変更する User.username = "jmwang"; ObjectInputStream is = new ObjectInputStream(new FileInputStream( "C:/user.txt")); user = (User) is.readObject(); //ストリームからUserデータを読み取る is.close(); System.out.println("\nread after Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class User implements Serializable { private static final long serialVersionUID = 8294180014912103005L; public static String username; private transient String passwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } }

実行結果は:

read before Serializable: 
username: Alexia
password: 123456

read after Serializable: 
username: jmwang
password: null

これは、デシリアライズ後のクラス内のstatic型変数usernameの値が、現在のJVM内の対応するstatic変数の値であり、変更後のjmwangであることを示しています。シリアライズ時の値であるAlexiaではありません。

3. transient 使用の詳細——transient キーワードで修飾された変数は本当にシリアライズできないのか?

次の例を考えてみてください:

例

import java.io.Externalizable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; /** * @descripiton Externalizableインターフェースの使用 * * @author Alexia * @date 2013-10-15 * */ 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) throws Exception { ExternalizableTest et = new ExternalizableTest(); ObjectOutput out = new ObjectOutputStream(new FileOutputStream( new File("test"))); out.writeObject(et); ObjectInput in = new ObjectInputStream(new FileInputStream(new File( "test"))); et = (ExternalizableTest) in.readObject(); System.out.println(et.content); out.close(); in.close(); } }

content変数はシリアライズされるのでしょうか?さて、答えを出力してしまいました。はい、実行結果は次のとおりです:

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

これはなぜでしょうか。クラスの変数がtransientキーワードで修飾されるとシリアライズできなくなると言っていたのではないでしょうか?

Javaでは、オブジェクトのシリアライズは2種類のインターフェースを実装することで実現できることを知っています。実装するのがSerializableインターフェースであれば、すべてのシリアライズは自動的に行われます。Externalizableインターフェースを実装する場合は、自動的にシリアライズされるものは何もなく、writeExternalメソッドでシリアライズしたい変数を手動で指定する必要があります。これはtransientで修飾されているかどうかとは関係ありません。したがって、2番目の例では、変数contentの初期化された内容が出力され、nullではありません。

出典:http://www.cnblogs.com/lanxuezaipiao/p/3369962.html