Como usar serialVersionUID no aprendizado de java

sobre serialVersionUID. Para que exatamente serve este campo? O que acontece se você não configurá-lo? Por que o "Manual de desenvolvimento do Alibaba Java" tem as seguintes disposições:

![-w934][4]

conhecimento prévio

Serializável e Externalizável

Uma classe implementa java.io.Serializablea interface para habilitar seus recursos de serialização. **Classes que não implementam esta interface não poderão ser serializadas ou desserializadas. ** Todos os subtipos de uma classe serializável são eles próprios serializáveis.

Se os leitores tiverem lido Serializableo código-fonte, descobrirão que é apenas uma interface vazia sem nada. **A interface serializável não possui métodos ou campos, e é usada apenas para identificar a semântica serializável. **No entanto, se uma classe não implementar esta interface e quiser ser serializada, java.io.NotSerializableExceptionuma exceção será lançada.

Como ele garante que apenas os métodos que implementam essa interface possam ser serializados e desserializados?

O motivo é que durante o processo de serialização, o seguinte código será executado:

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());
    }
}

Ao realizar uma operação de serialização, será julgado se a classe a ser serializada é Enum, Arraye Serializabletipo, e se não for, será lançada diretamente NotSerializableException.

As interfaces também são fornecidas em Java Externalizable, que também podem ser implementadas para fornecer recursos de serialização.

ExternalizableHerdado de Serializable, dois métodos abstratos são definidos nesta interface: writeExternal()e readExternal().

Ao usar Externalizablea interface para serialização e desserialização, os desenvolvedores precisam substituir writeExternal()um readExternal()método. Caso contrário, os valores de todas as variáveis ​​se tornarão valores padrão.

transitório

transientA função da palavra-chave é controlar a serialização da variável. Adicionar essa palavra-chave antes da declaração da variável pode impedir que a variável seja serializada no arquivo. Depois de desserializada, o valor da variável é definido como o valor inicial, como transienttipo int é 0, tipo de objeto é nulo.

estratégia de serialização personalizada

writeObjectDurante o processo de serialização, se os métodos e forem definidos na classe serializada , a máquina virtual tentará chamar os métodos e readObjectna classe de objeto para executar a serialização e desserialização definidas pelo usuário.writeObjectreadObject

Se não houver tal método, as chamadas padrão serão o método ObjectOutputStreamde defaultWriteObjecte o método ObjectInputStreamde defaultReadObject.

writeObjectOs métodos e definidos pelo usuário readObjectpermitem que os usuários controlem o processo de serialização, como alterar dinamicamente o valor serializado durante o processo de serialização.

Portanto, quando você precisa definir uma estratégia de serialização para alguns campos especiais, você pode considerar o uso de modificação transitória e reescrever os métodos writeObjecte readObjectvocê mesmo, como java.util.ArrayLista implementação em .

Encontramos aleatoriamente algumas classes em Java que implementam interfaces de serialização, como String, Integer, etc., e podemos encontrar um detalhe, ou seja, além de implementar essas classes, elas também definem um ![][5 Serializable] serialVersionUID

Então o que exatamente é isso serialVersionUID? Por que definir tal campo?

O que é serialVersionUID

A serialização é o processo de conversão das informações de estado de um objeto em um formato que pode ser armazenado ou transmitido. Todos sabemos que os objetos Java são armazenados na memória heap da JVM, ou seja, se a heap da JVM não existir, o objeto desaparecerá.

A serialização fornece uma solução que permite salvar objetos mesmo se a JVM estiver inativa. Assim como o disco U que costumamos usar. Serialize objetos Java em um formato que possa ser armazenado ou transmitido (como um fluxo binário), como salvar em um arquivo. Dessa forma, quando o objeto for necessário novamente, o fluxo binário será lido do arquivo e o objeto será desserializado do fluxo binário.

Se a máquina virtual permite a desserialização depende não apenas se o caminho de classe e o código da função são consistentes, mas também se os IDs de serialização das duas classes são consistentes. Esse chamado ID de serialização é definido no código serialVersionUID.

O que acontece se o serialVersionUID mudar

Vamos dar um exemplo e ver serialVersionUIDo que acontece se ele for modificado?

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;
    }
 }

Primeiro executamos o código acima e escrevemos um objeto User1 no arquivo. Em seguida, modificamos a classe User1 e serialVersionUIDalteramos o valor de 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;
    }
}

Em seguida, execute o seguinte código para desserializar os objetos no arquivo:

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();
            }
        }
    }
}

Os resultados da execução são os seguintes:

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

Pode-se descobrir que o código acima lança um java.io.InvalidClassExceptione aponta serialVersionUIDa inconsistência.

Isso porque, ao desserializar, a JVM irá comparar o fluxo de bytes de entrada serialVersionUIDcom a classe de entidade local correspondente serialVersionUID. Se forem iguais, é considerado consistente e pode ser desserializado, caso contrário, haverá uma exceção de sequência por inconsistências de versão, ou seja InvalidCastException.

É por isso que está estipulado no "Alibaba Java Development Manual" que durante as atualizações de compatibilidade, ao modificar as classes, não as modifique serialVersionUID. A menos que sejam duas versões completamente incompatíveis . Portanto, serialVersionUIDna verdade, está verificando a consistência da versão.

Se os leitores estiverem interessados, você pode dar uma olhada nos códigos JDK de cada versão. Essas classes compatíveis com versões anteriores serialVersionUIDnão foram alteradas. Por exemplo, a classe String serialVersionUIDsempre foi -6849794470754667710L.

No entanto, o autor acredita que essa especificação pode realmente ser mais rígida, ou seja, o regulamento:

Se uma classe implementa Serializablea interface, uma variável deve ser adicionada manualmente private static final long serialVersionUIDe o valor inicial definido.

Por que você precisa especificar explicitamente um serialVersionUID

serialVersionUIDVeja o que acontece se não definirmos explicitamente um na classe .

Tente modificar o código de demonstração acima, primeiro use a seguinte classe para definir um objeto, que não está definido nesta classe serialVersionUID, e grave-o no arquivo.

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

Em seguida, modificamos a classe User1 e adicionamos uma propriedade a ela. Tentando lê-lo do arquivo e desserializá-lo.

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;
    }
 }

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

Mais uma vez, lança InvalidClassExceptione aponta duas serialVersionUIDdiferenças, que são -2986778152837257883e 7961728318907695402.

Pode-se ver a partir daqui que o sistema adicionou um por si só serialVersionUID.

Assim, uma vez implementada a classe Serializable, é recomendável definir uma explicitamente serialVersionUID. Caso contrário, ao modificar a classe, ocorrerá uma exceção.

serialVersionUIDExistem dois métodos de geração de exibição:
um é o 1L padrão, por exemplo: private static final long serialVersionUID = 1L;
o outro é gerar um campo hash de 64 bits de acordo com o nome da classe, nome da interface, método e atributo do membro, etc., por exemplo:
private static final long serialVersionUID = xxxxL;

O último método pode ser gerado com a ajuda do IDE, que será apresentado posteriormente.

princípio por trás

Para saber o motivo, vamos dar uma olhada no código-fonte e analisar por que serialVersionUIDuma exceção é lançada ao alterar? serialVersionUIDDe onde vem o padrão quando não há definição explícita ?

Para simplificar a quantidade de código, a cadeia de chamadas para desserialização é a seguinte:

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

Em initNonProxy, o código da chave é o seguinte:

![][6]

Durante o processo de desserialização, serialVersionUIDsão feitas comparações e, se forem desiguais, uma exceção é lançada diretamente.

Conheça melhor getSerialVersionUIDo método:

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();
}

Quando não definido serialVersionUID, computeDefaultSUIDo método será chamado para gerar um padrão serialVersionUID.

Isso também encontra a raiz dos dois problemas acima.Na verdade, uma verificação estrita foi feita no código.

dicas de ideias

Para garantir que não esqueceremos a definição serialVersionUID, podemos ajustar a configuração do Intellij IDEA. Após implementar Serializablea interface, se não houver definição serialVersionUID, o IDEA (igual ao eclipse) solicitará: ![][7]

E você pode gerar um com um clique:

![][8]

Claro, esta configuração não entra em vigor por padrão, você precisa configurá-la manualmente no IDEA:

![][9]

No local marcado como 3 na figura (classe Serializable sem configuração serialVersionUID), marque-o e salve-o.

Resumir

serialVersionUIDÉ usado para verificar a consistência da versão. Portanto, ao fazer atualizações de compatibilidade, não altere serialVersionUIDo valor na classe.

Se uma classe implementa a interface Serializable, você deve se lembrar de defini-la serialVersionUID, caso contrário ocorrerá uma exceção. Você pode configurá-lo no IDE para permitir que ele o ajude a solicitar, e você pode gerar um rapidamente com um clique serialVersionUID.

O motivo da exceção ocorrer é porque a verificação é feita durante o processo de desserialização, e caso não esteja claramente definido, será gerado automaticamente um de acordo com as propriedades da classe.

Acho que você gosta

Origin blog.csdn.net/zy_dreamer/article/details/132307283
Recomendado
Clasificación