Java学習でserialVersionUIDを使用する方法

についてserialVersionUIDこのフィールドは一体何のためにあるのでしょうか? 設定しないとどうなりますか? なぜ「Alibaba Java 開発マニュアル」には次のような規定があるのでしょうか。

![-w934][4]

背景知識

直列化可能と外部化可能

クラスはjava.io.Serializableインターフェイスを実装して、シリアル化機能を有効にします。**このインターフェイスを実装していないクラスは、シリアル化または逆シリアル化できません。** 直列化可能なクラスのすべてのサブタイプは、それ自体直列化可能です。

読者がソース コードを読んだ場合はSerializable、それが何も含まれていない単なる空のインターフェイスであることがわかるでしょう。**シリアル化可能なインターフェイスにはメソッドやフィールドがなく、シリアル化可能なセマンティクスを識別するためにのみ使用されます。**ただし、クラスがこのインターフェイスを実装しておらず、シリアル化したい場合は、java.io.NotSerializableException例外がスローされます。

このインターフェイスを実装するメソッドのみがシリアル化および逆シリアル化できるようにするにはどうすればよいでしょうか?

その理由は、シリアル化プロセス中に次のコードが実行されるためです。

if (obj instanceof String) {
    writeString((String) obj, unshared);
} else if (cl.isArray()) {
    writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
    writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
    writeOrdinaryObject(obj, desc, unshared);
} else {
    if (extendedDebugInfo) {
        throw new NotSerializableException(
            cl.getName() + "\n" + debugInfoStack.toString());
    } else {
        throw new NotSerializableException(cl.getName());
    }
}

Enumシリアル化操作を行う際には、シリアル化するクラスが、型であるArrayどうかを判断しSerializable、そうでない場合は直接スローしますNotSerializableException

インターフェイスはJava でも提供されておりExternalizable、シリアル化機能を提供するために実装することもできます。

Externalizableから継承されSerializable、このインターフェイスではwriteExternal()との 2 つの抽象メソッドが定義されていますreadExternal()

Externalizableシリアル化と逆シリアル化にインターフェイスを使用する場合、開発者はメソッドwriteExternal()をオーバーライドする必要がありますreadExternal()それ以外の場合は、すべての変数の値がデフォルト値になります。

一時的な

transientキーワードの機能は、変数のシリアル化を制御することです。変数宣言の前にこのキーワードを追加すると、変数がファイルにシリアル化されるのを防ぐことができます。逆シリアル化された後、変数の値は次のような初期値に設定されます。transientint型は0、オブジェクト型はnullです。

カスタムシリアル化戦略

writeObjectシリアル化プロセス中に、およびメソッドがシリアル化されたクラスで定義されている場合、仮想マシンはreadObjectオブジェクト クラスのwriteObjectおよびメソッドを呼び出してreadObject、ユーザー定義のシリアル化と逆シリアル化を実行しようとします。

そのようなメソッドがない場合、デフォルトの呼び出しは のObjectOutputStreamメソッドdefaultWriteObjectと のObjectInputStreamメソッドですdefaultReadObject

ユーザー定義writeObjectおよびreadObjectメソッドを使用すると、ユーザーはシリアル化プロセス中にシリアル化された値を動的に変更するなど、シリアル化プロセスを制御できます。

writeObjectしたがって、いくつかの特殊なフィールドに対してシリアル化戦略を定義する必要がある場合は、一時的な変更を使用し、の実装readObjectなど、 および メソッドを自分で書き直すことを検討できます。java.util.ArrayList

String、Integer などのシリアル化インターフェイスを実装する Java クラスをランダムにいくつか見つけます。詳細を確認できます。つまり、これらのクラスの実装に加えて、![][5 ] も定義されていSerializableますserialVersionUID

それで、それは正確に何ですかserialVersionUIDなぜそのようなフィールドを設定するのでしょうか?

シリアルバージョンUIDとは何ですか

シリアル化は、オブジェクトの状態情報を保存または送信できる形式に変換するプロセスです。Java オブジェクトは JVM のヒープ メモリに格納されることは誰もが知っています。つまり、JVM ヒープが存在しない場合、オブジェクトは消滅します。

シリアル化は、JVM が停止している場合でもオブジェクトを保存できるソリューションを提供します。私たちが普段使っているUディスクと同じです。Java オブジェクトを、ファイルに保存するなど、保存または送信できる形式 (バイナリ ストリームなど) にシリアル化します。このようにして、オブジェクトが再度必要になると、バイナリ ストリームがファイルから読み取られ、オブジェクトはバイナリ ストリームから逆シリアル化されます。

仮想マシンが逆シリアル化を許可するかどうかは、クラスパスと関数コードが一貫しているかどうかだけでなく、2 つのクラスのシリアル化 ID が一貫しているかどうかにも依存します。このいわゆるシリアル化 ID は、コードで定義されていますserialVersionUID

SerialVersionUID が変更されるとどうなるか

例を挙げて、serialVersionUID変更するとどうなるかを見てみましょう。

public class SerializableDemo1 {
    public static void main(String[] args) {
        //Initializes The Object
        User1 user = new User1();
        user.setName("hollis");
        //Write Obj to File
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(oos);
        }
    }
}

class User1 implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
 }

まず上記のコードを実行し、User1 オブジェクトをファイルに書き込みます。次に、User1 クラスを変更し、serialVersionUIDの値を変更します2L

class User1 implements Serializable {
    private static final long serialVersionUID = 2L;
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

次に、次のコードを実行して、ファイル内のオブジェクトを逆シリアル化します。

public class SerializableDemo2 {
    public static void main(String[] args) {
        //Read Obj from File
        File file = new File("tempFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            User1 newUser = (User1) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(ois);
            try {
                FileUtils.forceDelete(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

実行結果は以下の通りです。

java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

上記のコードは をスローし、矛盾をjava.io.InvalidClassException指摘していることがわかります。serialVersionUID

これは、逆シリアル化するときに、JVM が受信バイト ストリームを対応するserialVersionUIDローカル エンティティ クラスとserialVersionUID比較するためです。それらが同じである場合、一貫性があるとみなされ、逆シリアル化できます。そうでない場合は、バージョンの不一致に対するシーケンス例外が発生します。InvalidCastException

このため、「Alibaba Java 開発マニュアル」では、互換性アップグレード中、クラスを変更する場合はクラスを変更しないように規定されていますserialVersionUID完全に互換性のない 2 つのバージョンでない限りしたがって、serialVersionUID実際にはバージョンの整合性を検証しています。

興味があれば、各バージョンの JDK コードを参照してください。これらの下位互換性のあるクラスはserialVersionUID変更されていません。たとえば、String クラスはserialVersionUID常に です-6849794470754667710L

ただし、著者は、この仕様は実際にはより厳格になる可能性がある、つまり次のような規制になる可能性があると考えています。

クラスがSerializableインターフェイスを実装する場合、変数を手動で追加しprivate static final long serialVersionUID、初期値を設定する必要があります。

なぜserialVersionUIDを明示的に指定する必要があるのですか

クラス内で明示的に定義しなかった場合にserialVersionUID何が起こるかを見てください。

上記のデモ コードを変更してみます。まず、次のクラスを使用して、このクラスでは定義されていないオブジェクトを定義し、serialVersionUIDそれをファイルに書き込みます。

class User1 implements Serializable {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
 }

次に、User1 クラスを変更し、プロパティを追加します。ファイルから読み取って逆シリアル化しようとしています。

class User1 implements Serializable {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
 }

結果:java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = -2986778152837257883, local class serialVersionUID = 7961728318907695402

もう一度、というInvalidClassException2 つの違いを捨てて指摘しますserialVersionUID-29867781528372578837961728318907695402

ここから、システムが自動的に 1 つを追加したことがわかりますserialVersionUID

したがって、クラスを実装したらSerializable、明示的に定義することをお勧めしますserialVersionUIDそうしないと、クラスを変更するときに例外が発生します。

serialVersionUID表示生成方法は 2 つあります。1
つはデフォルトの 1L です。private static final long serialVersionUID = 1L;
もう 1 つは、クラス名、インターフェイス名、メンバーのメソッドと属性などに従って 64 ビットのハッシュ フィールドを生成します。たとえば、次のとおりです。
private static final long serialVersionUID = xxxxL;

後者の方法は、後で紹介する IDE を使用して生成できます。

背後にある原則

その理由を知るために、ソース コードを見て、serialVersionUID変更時に例外がスローされる理由を分析してみましょう。明示的な定義がない場合、serialVersionUIDデフォルトはどこから来るのでしょうか?

コードの量を簡素化するために、逆シリアル化の呼び出しチェーンは次のようになります。

ObjectInputStream.readObject -> readObject0 -> readOrdinaryObject -> readClassDesc -> readNonProxyDesc -> ObjectStreamClass.initNonProxy

initNonProxyキーコードは次のとおりです。

![][6]

逆シリアル化プロセス中にserialVersionUID比較が行われ、それらが等しくないことが判明した場合は、例外が直接スローされます。

getSerialVersionUIDこのメソッドを詳しく見てみましょう。

public long getSerialVersionUID() {
    // REMIND: synchronize instead of relying on volatile?
    if (suid == null) {
        suid = AccessController.doPrivileged(
            new PrivilegedAction<Long>() {
                public Long run() {
                    return computeDefaultSUID(cl);
                }
            }
        );
    }
    return suid.longValue();
}

定義されていない場合serialVersionUIDcomputeDefaultSUIDメソッドが呼び出されてデフォルトのメソッドが生成されますserialVersionUID

これは、上記 2 つの問題の根本を見つけるものでもあり、実際、コード内では厳密な検証が行われています。

アイデアのヒント

定義を忘れないようにするためにserialVersionUID、Intellij IDEA の設定を調整できます。Serializableインターフェイスの実装後、定義がない場合serialVersionUID、IDEA (Eclipse と同じ) は次のプロンプトを表示します: ![][7]

ワンクリックで生成できます。

![][8]

もちろん、この構成はデフォルトでは有効になりません。IDEA で手動で設定する必要があります。

![][9]

図の 3 の部分 (serialVersionUID 設定のない Serializable クラス) にチェックを入れて保存します。

要約する

serialVersionUIDバージョンの一貫性を検証するために使用されます。serialVersionUIDしたがって、互換性のアップグレードを行う場合は、クラスの値を変更しないでください。

クラスが Serializable インターフェイスを実装している場合は、それを忘れずに定義する必要がありますserialVersionUID。そうしないと例外が発生します。IDE でこれを設定すると、プロンプトを表示できるようになり、ワンクリックですぐに生成できますserialVersionUID

例外が発生する理由は、デシリアライズ処理中に検証が行われるためで、明確に定義されていない場合はクラスのプロパティに応じて自動的に例外が生成されます。

おすすめ

転載: blog.csdn.net/zy_dreamer/article/details/132307283