正直に言うと、Java をしばらく勉強した友人は、transient というキーワードにまだあまり馴染みがなく、ほとんど使用したことがありません。しかし、キーワード transient は Java において不可欠な役割を果たしています。これについて話したい場合、最も登場する可能性が高いのは、IO ストリーム内のオブジェクト ストリーム (シリアル化されたストリームとも呼ばれる) に関してだと思います。
このキーワードに出会うまでは気にも留めない人も多いと思いますが、ブロガーが初めてこのキーワードに遭遇したのは、JDK のソース コードを読んでいたときだったと記憶しています。 Java の学習過程において、transient キーワードがまれである理由は、実際にはその機能と切り離すことができません。transient キーワードの主な機能は、transient キーワードによって変更された特定のメンバー属性変数がシリアル化されるのを防ぐことです。実際、まさにこの理由から、シリアル化操作は学習プロセス (通常は実際の開発) で使用されることはほとんどありません。連載に関しては、多くの初心者が戸惑ったり、具体的な概念がなかったりすると思いますが、問題ありません。次のブロガーが連載とは何かを明確に思い出させ、一生忘れることがないようにします(ちょっと大袈裟、ちょっと大げさ、殴られそうだ)
1. 連載とは?
シリアル化と言えば、それに付随するもう 1 つの概念が逆シリアル化です。パニックにならないでください、小さな白い子供用の靴。シリアル化を思い出すことは、逆シリアル化を思い出すことと同じです。なぜなら、逆シリアル化はシリアル化の逆だからです。ブロガーは、混乱を避けるために、シリアル化の概念だけを覚えておくことをお勧めします。
(おすすめビデオ: java ビデオ チュートリアル)
専門用語の定義の連載 :
Java は、オブジェクトをシリアル化するためのメカニズムを提供します。オブジェクトは、オブジェクトのデータ、オブジェクトのタイプ、オブジェクトに格納されている属性などの情報を含むバイト シーケンスで表すことができます。バイト シーケンスがファイルに書き込まれた後は、ファイル内のオブジェクトの情報を永続化することと同じになります。逆に、バイト シーケンスをファイルから読み戻し、オブジェクトを再構築し、逆シリアル化することもできます。オブジェクトのデータ、オブジェクトのタイプ、およびオブジェクトに格納されているデータ情報はすべて、メモリ内にオブジェクトを作成するために使用できます。
Serialization: Byte——> Object
実際、私が要約したのは上記の結論です。理解できない場合は、専門用語の定義を直接参照してください。理解したら覚えてください。私の言葉だけ覚えておいてください。覚えていない場合は私を殴り殺してください。
連載を理解するための図:
2. なぜシリアル化するのか?
シリアル化の概念については前のセクションで説明しましたが、その概念を理解した後は、なぜシリアル化する必要があるのかを知る必要があります。
連載が必要な理由を話す前に、ブロガーとして栗を贈らせてください:
食料品を買いに街に行くときと同じように、一般的な操作は次のとおりです。家に帰るまでビニール袋に入れておいて、調理する時間になったら食器を取り出します。そして、この一連の操作はシリアル化とデシリアル化のようなものです。
Java におけるオブジェクトのシリアル化とは、オブジェクトをバイト シーケンスに変換することを指します。これらのバイト シーケンスには、オブジェクトのデータと情報が含まれています。シリアル化されたオブジェクトは、データベースやファイルに書き込むことができ、ネットワークにも使用できます一般に、キャッシュを使用する場合 (十分なメモリ領域がハードディスク上にローカルに保存されていない可能性があります)、またはリモートで rpc (ネットワーク送信) を呼び出す場合、多くの場合、エンティティ クラスに Serializable インターフェイスを実装させる必要があります。シリアル化可能です。
開発プロセス中に一時的なキーワードで変更されたチェストナット:
ユーザーがパスワードやその他の情報を持っている場合、セキュリティ上の理由から、ネットワーク操作中にそれらの情報が送信されることは望ましくありません。これらの情報は、変数に transient キーワードを追加できます。つまり、このフィールドのライフサイクルは呼び出し元のメモリ内にのみ存在し、永続化のためにディスクに書き込まれることはありません。
開発プロセス中に一時的なキーワード変更を必要としない例:
1. クラス内のフィールド値は、他のフィールドに基づいて派生できます。
2. 特定のビジネス要件に応じて、どのフィールドをシリアル化したくないのか;
なぜシリアル化すべきではないのか考えたことがあるでしょうか?実際、それは主にストレージスペースを節約するためです。プログラムを最適化しましょう!
PS: HashMap のソース コードを見ていたら、フィールドが一時的なもので変更されていたことを思い出しました。それは意味があると思います。実際には、modCount フィールドは意味がないため、シリアル化する必要はありませんModCount は主に、HashMap が変更されたかどうかを判断するために使用されます (modCount は、挿入および削除操作中に増加します)。この種の変数の場合、先頭には任意の値を指定できますが、もちろん 0 (新しい、逆シリアル化された、またはクローンされた) ). 出力されるときは常に 0)、その値を永続化する必要はありません。
もちろん、シリアル化後の最終的な目的は、シリアル化を解除して元の Java オブジェクトに戻すことです。そうでない場合、シリアル化後に何をしますか? 食料品を買うのと同じように、ビニール袋に包むのは安全に家に帰るためです。ビニール袋を取り除き、シリアル化されたバイト シーケンスを Java オブジェクトに復元できるようにします。このプロセスは逆シリアル化です。
3. シリアル化と一時的なものの使用
1. シリアル化する必要があるオブジェクトのクラスは、シリアル化インターフェイス (Java.lang.Serializable インターフェイス) を実装する必要があります。 (抽象メソッドを持たないフラグ インターフェイス)、Java のほとんどのクラス (String、Integer クラスなど) がこのインターフェイスを実装します。このインターフェイスを実装していないクラスは、状態をシリアル化または逆シリアル化せず、NotSerializableException 例外をスローします。
2. 最下位層は、現在のオブジェクトが Serializable のインスタンスであるかどうかを判断し、Java オブジェクトの instanceof Serializable を使用してシリアル化が許可されます。
3. Java でオブジェクト ストリーム ObjectOutputStream を使用して、シリアル化と ObjectInputStream ストリームの逆シリアル化を完了します
==ObjectOutputStream: writeObject() メソッドによるシリアル化操作==
==ObjectInputStream: readObject() メソッドによる逆シリアル化操作==
4. このクラスのすべてのプロパティはシリアル化可能である必要があります。シリアル化可能にする必要のない属性がある場合は、その属性を一時的としてマークし、transient キーワードを使用して変更する必要があります。
バイトのため、ストリーム操作が必要です。つまり、オブジェクト ストリームはシリアル化されたストリーム ObjectOutputstream とも呼ばれます。さまざまな状況でシリアル化を分析してみましょう. アクションコード!
ここで、宜春ブログを読んでいる読者に、ノックしてみてください。一目見て忘れずに持ち歩くか、コピーして実行してください。特に小さな白い子供の靴の場合は、私を信じてください。 !きっと違う何かが得られるはずです。 !
3.1. Serializable インターフェイスを実装しないシリアル化
package TransientTest; import java.io.*; class UserInfo { //================================注意这里没有实现Serializable接口 private String name; private transient String password; public UserInfo(String name, String psw) { this.name = name; this.password = psw; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", password='" + password + '\'' + '}'; } } public class TransientDemo { public static void main(String[] args) { UserInfo userInfo = new UserInfo("老王", "123"); System.out.println("序列化之前信息:" + userInfo); try { ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("userinfo.txt")); output.writeObject(new UserInfo("老王", "123")); output.close(); } catch (IOException e) { e.printStackTrace(); } } }
実行結果
3.2. Serializable インターフェイスを使用したシリアル化
Serializable インターフェイスを追加して再度実行すると、プロジェクトに表示される userinfo.txt ファイルの内容が次のようになっていることがわかります:
実際には重要なのは、シリアル化操作が成功したということです。
3.3. 通常のシリアル化状況
package TransientTest; import java.io.*; class UserInfo implements Serializable { //第一步实现Serializable接口 private String name; private String password; //都是普通属性============================== public UserInfo(String name, String psw) { this.name = name; this.password = psw; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", password='" + password + '\'' + '}'; } } public class TransientDemo { public static void main(String[] args) throws ClassNotFoundException { UserInfo userInfo = new UserInfo("程序员老王", "123"); System.out.println("序列化之前信息:" + userInfo); try { ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步开始序列化操作 output.writeObject(new UserInfo("程序员老王", "123")); output.close(); } catch (IOException e) { e.printStackTrace(); } try { ObjectInputStream input = new ObjectInputStream(new FileInputStream("userinfo.txt")); //第三步开始反序列化操作 Object o = input.readObject(); //ObjectInputStream的readObject方法会抛出ClassNotFoundException System.out.println("序列化之后信息:" + o); } catch (IOException e) { e.printStackTrace(); } } }
実行結果:
序列化之前信息:UserInfo{name='程序员老王', password='123'} 序列化之后信息:UserInfo{name='程序员老王', password='123'}
3.4. 一時的なシリアル化状況
package TransientTest; import java.io.*; class UserInfo implements Serializable { //第一步实现Serializable接口 private String name; private transient String password; //特别注意:属性由transient关键字修饰=========== public UserInfo(String name, String psw) { this.name = name; this.password = psw; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", password='" + password + '\'' + '}'; } } public class TransientDemo { public static void main(String[] args) throws ClassNotFoundException { UserInfo userInfo = new UserInfo("程序员老王", "123"); System.out.println("序列化之前信息:" + userInfo); try { ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步开始序列化操作 output.writeObject(new UserInfo("程序员老王", "123")); output.close(); } catch (IOException e) { e.printStackTrace(); } try { ObjectInputStream input = new ObjectInputStream(new FileInputStream("userinfo.txt")); //第三步开始反序列化操作 Object o = input.readObject(); //ObjectInputStream的readObject方法会抛出ClassNotFoundException System.out.println("序列化之后信息:" + o); } catch (IOException e) { e.printStackTrace(); } } }
実行結果:
序列化之前信息:UserInfo{name='程序员老王', password='123'} 序列化之后信息:UserInfo{name='程序员老王', password='null'}
結果には特に注意してください。一時的な変更で追加された属性値はデフォルト値 null です。 transient によって変更された属性が int 型の場合、シリアル化後の値は 0 でなければなりません。もちろん、試してみることもできます。これは何を意味しますか?これは、一時的としてマークされた属性は、オブジェクトがシリアル化されるときに保存されない (または変数が永続化されない) ことを意味します。
3.5. 静的シリアル化の状況
package TransientTest; import java.io.*; class UserInfo implements Serializable { //第一步实现Serializable接口 private String name; private static String password; //特别注意:属性由static关键字修饰============== public UserInfo(String name, String psw) { this.name = name; this.password = psw; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", password='" + password + '\'' + '}'; } } public class TransientDemo { public static void main(String[] args) throws ClassNotFoundException { UserInfo userInfo = new UserInfo("程序员老王", "123"); System.out.println("序列化之前信息:" + userInfo); try { ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步开始序列化操作 output.writeObject(new UserInfo("程序员老王", "123")); output.close(); } catch (IOException e) { e.printStackTrace(); } try { ObjectInputStream input = new ObjectInputStream(new FileInputStream("userinfo.txt")); //第三步开始反序列化操作 Object o = input.readObject(); //ObjectInputStream的readObject方法会抛出ClassNotFoundException System.out.println("序列化之后信息:" + o); } catch (IOException e) { e.printStackTrace(); } } }
実行結果:
序列化之前信息:UserInfo{name='程序员老王', password='123'} 序列化之后信息:UserInfo{name='程序员老王', password='123'}
このとき、静的変更もシリアル化されていると誤解しますが、実際にはそうではありません。実際、ここで混同されやすいのです。明らかに null (デフォルト値) を取り出すと、シリアル化されないことがわかります。ここでは明らかにデフォルト値に変更されていないのに、なぜ静的はシリアル化されないと言えるのですか?
実際には、逆シリアル化後のクラスの静的変数名の値は、実際には現在の JVM の対応する静的変数の値です。この値は JVM 内にあり、逆シリアル化から派生したものではありません。つまり、static によって変更された変数はシリアル化に参加しません。しかし、証拠がなければそれを言うことはできません。はい、2 つのプログラムを比較してみましょう。そうすれば理解できるでしょう。
最初のプログラム: これは、静的によって変更されていない名前属性プログラムです:
package Thread; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; class UserInfo implements Serializable { private String name; private transient String psw; public UserInfo(String name, String psw) { this.name = name; this.psw = psw; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPsw() { return psw; } public void setPsw(String psw) { this.psw = psw; } public String toString() { return "name=" + name + ", psw=" + psw; } } public class TestTransient { public static void main(String[] args) { UserInfo userInfo = new UserInfo("程序员老过", "456"); System.out.println(userInfo); try { // 序列化,被设置为transient的属性没有被序列化 ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt")); o.writeObject(userInfo); o.close(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } try { //在反序列化之前改变name的值 =================================注意这里的代码 userInfo.setName("程序员老改"); // 重新读取内容 ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt")); UserInfo readUserInfo = (UserInfo) in .readObject(); //读取后psw的内容为null System.out.println(readUserInfo.toString()); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
実行結果:
name=程序员老过, psw=456name=程序员老过, psw=null
からの実行結果図からわかるように、プログラムは逆シリアル化する前に name の値を変更しようとしましたが、失敗しました。
2 番目のプログラム: これは静的に変更された名前属性プログラムです:
package Thread; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; class UserInfo implements Serializable { private static final long serialVersionUID = 996890129747019948 L; private static String name; private transient String psw; public UserInfo(String name, String psw) { this.name = name; this.psw = psw; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPsw() { return psw; } public void setPsw(String psw) { this.psw = psw; } public String toString() { return "name=" + name + ", psw=" + psw; } } public class TestTransient { public static void main(String[] args) { UserInfo userInfo = new UserInfo("程序员老过", "456"); System.out.println(userInfo); try { // 序列化,被设置为transient的属性没有被序列化 ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt")); o.writeObject(userInfo); o.close(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } try { //在反序列化之前改变name的值 userInfo.setName("程序员老改"); // 重新读取内容 ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt")); UserInfo readUserInfo = (UserInfo) in .readObject(); //读取后psw的内容为null System.out.println(readUserInfo.toString()); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
実行結果:
name=程序员老过, psw=456name=程序员老改, psw=null
プログラムの実行結果から次のようになります。デシリアライゼーションの前に name の値を変更しようとしていることがプログラマによって行われ、結果は成功していることがわかります。 2 つのプログラムを比較すると、よくわかりますか?
static キーワードによって変更されたメンバー属性は、非静的メンバー属性よりもメモリにロードされやすくなります。同時に、static はメモリに入るオブジェクトよりも優れています。static によって変更されたメンバー変数はシリアル化できず、すべてのオブジェクトはシリアル化されません。静的変数はオブジェクトの状態の一部ではないため、シリアル化には関与しません。したがって、静的変数を一時変数として宣言しても意味がありません。したがって、逆シリアル化後のクラスの静的変数名の値は、実際には現在の JVM 内の対応する静的変数の値になります。この値は JVM 内にあり、逆シリアル化から派生したものではありません。
3.6. 最終的なシリアル化の状況
final キーワードの場合、final 変数は値を通じてシリアル化に直接参加します コード プログラムについては、これ以上投稿しません。最終的な修正を確認してください。
注意すべき主な点は、final と transient は同じ変数を同時に変更でき、結果は同じであるということです。transient には影響しません。ここでは主にそれについて説明します。将来、開発中にこのような状況に遭遇した場合でも混乱しないようにしてください。
4. Java クラスにおける SerialVersionUID の役割
transient キーワードが言及されているため、シリアル化について言及する必要があります。シリアル化について言及しているため、serialVersionUID について言及する必要があります。これは何ですか? ?毛織物?基本的にシリアル化があればこのserialVersionUIDが存在します。
#serialVersionUID は Java のシリアル化メカニズムに適用されます。簡単に言うと、Java のシリアル化メカニズムは、クラスの SerialVersionUID を判断することによってバージョンの一貫性を検証します。逆シリアル化するとき、JVM は渡されたバイト ストリームの SerialVersionUID を、対応するローカル エンティティ クラスの SerialVersionUID と比較します。それらが同じである場合、それらは一貫しているとみなされ、逆シリアル化できます。そうでない場合は、シリアル化されたバージョンが表示されます。一貫性のない例外、 InvalidCastException は開発中に記述できる場合と記述できない場合がありますが、記述することをお勧めします。
5. トランジェント キーワードの概要
1. 変数がトランジェントによって変更された場合、変数はシリアル化されません
2. トランジェントキーワードのみ 変数は変更できますが、メソッドとクラスは変更できません。
3. static キーワードによって変更された変数はシリアル化に参加しません. 静的変数は、transient によって変更されたかどうかに関係なく、シリアル化できません。
4. 最終変数値はシリアル化に参加し、最終トランジェントは同時に変数を変更します。ファイナルはトランジェントには影響せず、シリアル化にも参加しません。
2 番目のポイントは、次の点です。注: ローカル変数は、transient キーワードでは変更できません。変数がユーザー定義のクラス変数である場合、クラスは Serializable インターフェイスを実装する必要があります。
3 番目の注意点は、逆シリアル化後のクラス内の静的変数の値は、実際には、対応する静的変数であるということです。現在の JVM 値は JVM 内にあり、逆シリアル化から派生したものではありません。
結論: transient キーワードで変更するとシリアル化されず、ストレージ容量を節約できるという利点があります。プログラムを最適化しましょう!次に、transient によって変更されたフィールドが再計算され、初期化されます。
この記事は、php 中国語 Web サイトの java チュートリアル コラムからのものです。学習へようこそ!
以上がJavaのtransientキーワードの詳しい説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。