CodeGym/Java Blog/ランダム/Java におけるシリアル化と逆シリアル化の違いは何ですか?
John Squirrels
レベル 41
San Francisco

Java におけるシリアル化と逆シリアル化の違いは何ですか?

ランダム グループに公開済み
人のメンバー
やあ!今日のレッスンでは、Java でのシリアル化と逆シリアル化について説明します。簡単な例から始めます。あなたがコンピューター ゲームを作成したとします。90 年代に育ち、その時代のゲーム コンソールを覚えているなら、おそらく、ゲームを保存したりロードしたりする機能、つまり今日私たちが当然と思っている機能がゲーム コンソールに欠けていたことをご存じでしょう :) そうでない場合は、想像してみてください。 Java におけるシリアル化と逆シリアル化の違いは何ですか?  - 1 今では、これらの能力のないゲームは駄目になるのではないかと思います。そもそも、ゲームを「保存」したり「ロード」したりすることは何を意味するのでしょうか? まあ、普通の意味はわかります。中断したところからゲームを続けたいということです。これを行うには、一種の「チェック ポイント」を作成し、それを使用してゲームをロードします。しかし、カジュアルなゲーマーではなくプログラマーにとって、これは何を意味するのでしょうか? 答えは簡単です。「私たちは」。Strategium でスペインとしてプレイしているとします。ゲームには状態があります。誰がどの領土を所有しているか、誰がどれだけのリソースを持っているか、誰が誰と同盟を結んでいるか、誰が誰と戦争中であるかなどです。将来復元してゲームを続行するには、この情報、つまりプログラムの状態を何らかの方法で保存する必要があります。まさにこれがシリアル化逆シリアル化の目的だからです。 シリアル化は、オブジェクトの状態をバイトのシーケンスに保存するプロセスです。 逆シリアル化これらのバイトからオブジェクトを復元するプロセスです。任意の Java オブジェクトをバイト シーケンスに変換できます。なぜそれが必要なのでしょうか? プログラムはそれ自体では存在しない、ということを何度も述べてきました。ほとんどの場合、他のプログラムと対話したり、データを交換したりします。また、バイト シーケンスは便利で効率的な形式です。たとえば、SavedGameオブジェクトをバイトのシーケンスに変換し、これらのバイトをネットワーク経由で別のコンピュータに送信し、2 番目のコンピュータでこれらのバイトを Java オブジェクトに戻すことができます。難しそうですよね?そして、このプロセスの実装は面倒に思えます :/ 幸いなことに、そうではありません。:) Java では、Serializableインターフェイスはシリアル化プロセスを担当します。このインターフェイスは非常にシンプルです。これを使用するために単一のメソッドを実装する必要はありません。ゲーム保存クラスは次のように単純に見えます。
import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoriesInfo;
   private String[] resourcesInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoriesInfo, String[] resourcesInfo, String[] diplomacyInfo){
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(String[] territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public String[] getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(String[] resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

   public String[] getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(String[] diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + Arrays.toString(territoriesInfo) +
               ", resourcesInfo=" + Arrays.toString(resourcesInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
3 つの配列は、領土、資源、外交に関する情報を担当します。Serializable インターフェイスは Java 仮想マシンに「すべて問題ありません。必要に応じて、このクラスのオブジェクトをシリアル化できます」と伝えます。インターフェイスが 1 つも存在しないインターフェイスは奇妙に見えます :/ なぜ必要なのでしょうか? この質問に対する答えは上で見ることができます。これは、Java 仮想マシンに必要な情報を提供するだけの役割を果たします。以前のレッスンの 1 つで、マーカー インターフェイスについて簡単に説明しました。これらは、将来 Java マシンに役立つ追加情報をクラスにマークするだけの特別な情報インターフェイスです。実装する必要があるメソッドはありません。Serializableはそれらのインターフェイスの 1 つです。private static final long serialVersionUIDもう 1 つの重要な点:クラスで定義した変数がなぜ必要なのでしょうか? なぜ必要なのでしょうか? このフィールドには、シリアル化されたクラスのバージョンの一意の識別子が含まれます。インターフェイスを実装するクラスには識別子Serializableがありますversion。これは、クラスの内容 (フィールド、宣言された順序、メソッドなど) に基づいて計算されます。クラス内のフィールドのタイプやフィールドの数を変更すると、バージョン識別子はすぐに変更されます。 。serialVersionUIDクラスがシリアル化されるときにも書き込まれます。逆シリアル化、つまり一連のバイトからオブジェクトを復元しようとすると、関連付けられたものが次のserialVersionUID値と比較されます。serialVersionUID私たちのプログラムのクラスのために。値が一致しない場合は、java.io. InvalidClassExceptionがスローされます。以下にその例を見ていきます。これを回避するには、クラスでバージョン識別子を手動で設定するだけです。この例では、単純に 1 になります (ただし、他の任意の数値に置き換えることもできます)。さて、オブジェクトをシリアル化して何が起こるかを見てみましょうSavedGame
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       String[] territoryInfo = {"Spain has 6 provinces", "Russia has 10 provinces", "France has 8 provinces"};
       String[] resourcesInfo = {"Spain has 100 gold", "Russia has 80 gold", "France has 90 gold"};
       String[] diplomacyInfo = {"France is at war with Russia, Spain has taken a neutral position"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourcesInfo, diplomacyInfo);

       // Create 2 streams to serialize the object and save it to a file
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       // Save the game to a file
       objectOutputStream.writeObject(savedGame);

       // Close the stream and free resources
       objectOutputStream.close();
   }
}
ご覧のとおり、FileOutputStreamと の2 つのストリームを作成しましたObjectOutputStream。1 つ目はデータをファイルに書き込むことができ、2 つ目はオブジェクトをバイトに変換します。同様の「ネストされた」構造 (例: ) については、前のレッスンですでに見たので、怖がる必要はありません :) このような 2 つのストリームの「チェーン」を作成することで、両方のタスクを実行します。オブジェクトをセットにnew BufferedReader(new InputStreamReader(...))変換します。SavedGameのバイト数を取得し、 メソッドを使用してファイルに保存しますwriteObject()。ちなみに、私たちは何が得られたかさえ見ていませんでした。ファイルを見てみましょう。 *注意: 事前にファイルを作成する必要はありません。その名前のファイルが存在しない場合は、自動的に作成されます*。 その内容は次のとおりです。
¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
ああ :( 私たちのプログラムは動作しなかったようです :( 実際、動作しました。単なるオブジェクトやテキストではなく、一連のバイトをファイルに送信したことを覚えていますか? そうですね、これがその内容です。バイトのセットは次のようになります:) これは保存されたゲームです! 元のオブジェクトを復元したい場合、つまり中断したところからゲームを開始して続行したい場合は、逆のプロセスが必要です: deserialization。場合:
import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);
   }
}
そして結果がこれです!
SavedGame{territoriesInfo=["Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces], resourcesInfo=[Spain has 100 gold, Russia has 80 gold, France has 90 gold], diplomacyInfo=[France is at war with Russia, Spain has taken a neutral position]}
素晴らしい!まずゲームの状態をファイルに保存し、次にそのファイルから復元することができました。次に、クラスのバージョン識別子を使用せずに同じことを実行してみましょうSavedGame。両方のクラスを書き直すことはありません。コードは同じままですが、クラスprivate static final long serialVersionUIDから削除しますSavedGameシリアル化後のオブジェクトは次のとおりです。
¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
しかし、それを逆シリアル化しようとすると何が起こるかを見てください。
InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
これは上で述べたまさに例外です。ところで、重要なことを見逃していました。文字列とプリミティブが簡単にシリアル化できることは理にかなっています。おそらく、Java にはこれを行うための何らかの組み込みメカニズムが備わっているでしょう。しかし、serializableクラスにプリミティブではなく、他のオブジェクトへの参照があるフィールドがある場合はどうなるでしょうか? たとえば、クラスと連携するためにTerritoriesInfoResourcesInfoおよびクラスを個別に作成してみましょう。 DiplomacyInfoSavedGame
public class TerritoriesInfo {

   private String info;

   public TerritoriesInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "TerritoriesInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class ResourcesInfo {

   private String info;

   public ResourcesInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "ResourcesInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class DiplomacyInfo {

   private String info;

   public DiplomacyInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "DiplomacyInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}
ここで疑問が生じます:変更したクラスをシリアル化したい場合、これらすべてのクラスが必要なのでしょうか?SerializableSavedGame
import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoriesInfo getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(TerritoriesInfo territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public ResourcesInfo getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(ResourcesInfo resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(DiplomacyInfo diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + territoriesInfo +
               ", resourcesInfo=" + resourcesInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
さて、テストしてみましょう!すべてをそのままにして、SavedGameオブジェクトをシリアル化してみましょう。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
結果:
Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
うまくいきませんでした!基本的に、それが私たちの質問に対する答えです。オブジェクトがシリアル化されると、そのインスタンス変数によって参照されるすべてのオブジェクトがシリアル化されます。 また、それらのオブジェクトが他のオブジェクトも参照している場合、それらもシリアル化されます。など、無限に続きます。このチェーン内のすべてのクラスは でなければなりませんSerializable。そうしないと、クラスをシリアル化できず、例外がスローされます。ちなみに、これは将来的に問題を引き起こす可能性があります。たとえば、シリアル化するときにクラスの一部が必要ない場合はどうすればよいでしょうか? あるいは、たとえば、TerritoryInfoクラスがサードパーティのライブラリの一部として提供された場合はどうなるでしょうか。さらに、そうではなくSerializable、したがってそれを変更できないと仮定します。TerritoryInfoフィールドを追加できないことがわかりました。SavedGameなぜなら、そうするとクラス全体がSavedGameシリアル化できなくなるからです。それは問題です :/ Java におけるシリアル化と逆シリアル化の違いは何ですか?  - 2Java では、この種の問題はtransientキーワードを使用して解決されます。このキーワードをクラスのフィールドに追加すると、そのフィールドはシリアル化されません。クラスのインスタンス フィールドの1 つを一時的なものにしてみましょうSavedGame。次に、1 つのオブジェクトをシリアル化して復元します。
import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   // ...getters, setters, toString()
}



import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}


import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);

       objectInputStream.close();


   }
}
そして結果は次のとおりです。
SavedGame{territoriesInfo=null, resourcesInfo=ResourcesInfo{info='Spain has 100 gold, Russia has 80 gold, France has 90 gold'}, diplomacyInfo=DiplomacyInfo{info='France is at war with Russia, Spain has taken a neutral position'}}
さらに、フィールドにどのような値が割り当てられるかについての質問に対する答えも得られましたtransient。デフォルト値が割り当てられます。オブジェクトの場合、これは ですnull。数分の時間に余裕があるときに、連載に関するこの優れた記事を読むことができます。インターフェイスについても説明しますExternalizable。これについては次のレッスンで説明します。さらに、書籍『Head-First Java』には、このトピックに関する章があります。ちょっと注意してください:)
コメント
  • 人気
  • 新規
  • 古い
コメントを残すには、サインインしている必要があります
このページにはまだコメントがありません