Artigo Diretório
Recentemente, estou fazendo um projeto de gerenciamento de supermercado, armazenando dados no banco de dados Redis por meio de serialização. Ao implementar a função de modificação das informações do usuário, a fim de facilitar a modificação do tipo de dado Date para o tipo String, foi reportado um erro.Após a abertura do console, ocorreu o seguinte erro.
java.io.InvalidClassException: com.li.pojo.SmbmsUser;
local class incompatible: stream classdesc serialVersionUID = 2416888619525883151,
local class serialVersionUID = 516098953370879925
Causa do erro: ID inconsistente antes e depois da serialização e a desserialização resultou em um erro.
Agora que há um erro, ele deve ser resolvido. O entendimento anterior de serialização e desserialização apenas se baseava em
saber como usá-lo. Vamos aprender mais hoje.
1. Primeiro saiba o que são serialização e desserialização
Ao aprender novos pontos de conhecimento, devemos sempre ter dúvidas: o que, por que, como fazer e três aspectos para conseguir isso. Só então ficará claro e claro, e a memória será mais profunda.
-
O que é serialização e desserialização?
Serialização: é converter o objeto em uma seqüência de caracteres.
Desserialização: é restaurar a seqüência de caracteres para o objeto original
-
Por que serialização e desserialização?
Conforme mencionado anteriormente, ao fazer projetos ssm, a serialização era usada para converter objetos em sequências de caracteres e armazenados no banco de dados Redis
. O que devemos fazer se a serialização não for usada, use o objeto de armazenamento map (key -value), pode ser usado quando não há muitos dados em uma única tabela e a quantidade de dados é muito problemática. Portanto, é recomendável armazenar objetos serializados no Redis quando a quantidade de dados for particularmente
grande.Geralmente, a serialização é usada para ser gravada em bancos de dados, arquivos e usada para transmissão de rede. -
O que posso fazer para serializá-lo?
Implementar interface serializável
public interface Serializable {
}
Serializável é claramente uma interface vazia. Na verdade, a função dessa interface é informar à JVM que desejo serializar e desserializar.
A operação específica é a seguinte:
Prepare a classe de entidade
public class Student {
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
teste:
//初始化
Student student = new Student();
student.setName("张三");
student.setAge(12);
System.out.println(student.toString());
//序列化
try {
//把对象写入文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\imag\\b.txt"));
oos.writeObject(student);
System.out.println("序列化成功!");
} catch (IOException e) {
e.printStackTrace();
}
//反序列化
try {
//从文件中读出对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\imag\\b.txt"));
Student stu = (Student) ois.readObject();
System.out.println("反序列化成功!");
System.out.println(stu.toString());
} catch (Exception e) {
e.printStackTrace();
}
A interface serializável não está implementada e um erro é relatado.
java.io.NotSerializableException: com.li.pojo.Student
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.li.test.SerializableTest.serializable(SerializableTest.java:25)
at com.li.test.SerializableTest.main(SerializableTest.java:14)
Implementar interface serializável
public class Student implements Serializable{
//省略属性
//省略setter、getter方法
//省略toString方法
}
Imprima o objeto antes da serialização:
Aluno {nome = 'Zhang San', idade = 12}
A serialização foi bem-sucedida! E b.txt tem algum conteúdo
Em seguida, chame a desserialização e a
desserialização do objeto de saída será bem-sucedida!
Aluno {nome = 'Zhang San', idade = 12}
Após a desserialização, ele era consistente com o objeto antes da serialização!
2. O processo de operação específico de serialização e desserialização
Como a serialização e a desserialização específicas são realizadas?
- O processo de execução de serialização específico é o seguinte (a lista de parâmetros é omitida):
ObjectOutputStream-> writeObject () -> writeObject0 () -> writeOrdinaryObject () -> writeSerialData () -> defaultWriteFields ()
O método writeObject0 julga
se é um tipo String, um tipo de enumeração, um objeto serializado, se não for, ele lançará A exceção
NotSerializableException é muito familiar? A exceção de que não implementamos a interface Serializable lançada é daqui.
// remaining cases
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());
}
}
- O processo de execução de desserialização específico é o seguinte (a lista de parâmetros é omitida):
ObjectInputStream -> readObject () -> readObject0 () -> readOrdinaryObject () -> readSerialData () -> defaultReadFields ()
3. Por que as propriedades modificadas por transiente e estático não podem ser serializadas?
Teste duas palavras-chave:
- estático
- transitório
//新增加两个字段
public static String addRess="宁夏";
private transient String carName="捷豹";
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", addRess='" + addRess + '\'' +
", carName='" + carName + '\''+
'}';
}
//初始化
Student student = new Student();
student.setName("张三");
student.setAge(12);
System.out.println(student.toString());
//序列化
try {
//把对象写入文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\imag\\b.txt"));
oos.writeObject(student);
System.out.println("序列化成功!");
} catch (IOException e) {
e.printStackTrace();
}
student.setAddRess("陕西");
//反序列化
try {
//从文件中读出对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\imag\\b.txt"));
Student stu = (Student) ois.readObject();
System.out.println("反序列化成功!");
System.out.println(stu.toString());
} catch (Exception e) {
e.printStackTrace();
}
Resultado de impressão de saída:
//序列化前的对象
Student{
name='张三', age=12, addRess='宁夏', carName='捷豹'}
序列化成功!
反序列化成功!
//反序列化后的对象
Student{
name='张三', age=12, addRess='陕西', carName='null'}
A partir dos resultados, podemos comparar:
Antes de serialização: addRess
o valor do '宁夏'
campo '陕西'
, modificar o valor do campo após a serialização, e o valor do campo após a desserialização '陕西'
, não o estado antes desserialização.
Por quê? O campo modificado estático original pertence ao estado da classe e a serialização é para o estado do objeto, portanto, a palavra-chave modificada pela palavra-chave estática não salva o estado
antes da serialização. carName
O valor do campo antes da ''捷豹
serialização e o valor do campo após a serialização são null. Por quê?
O campo transiente original (temporário) modificado por palavra-chave pode impedir que o campo seja serializado no arquivo. Depois de ser desserializado, o valor do campo transiente é definido como o valor inicial. Por exemplo, o valor inicial do tipo int é 0 e o tipo de objeto O valor inicial de é nulo.
Se você quiser explorar, isso também se reflete no código-fonte
/**
* Returns array of ObjectStreamFields corresponding to all non-static
* non-transient fields declared by given class. Each ObjectStreamField
* contains a Field object for the field it represents. If no default
* serializable fields exist, NO_FIELDS is returned.
*/
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT;
for (int i = 0; i < clFields.length; i++) {
if ((clFields[i].getModifiers() & mask) == 0) {
list.add(new ObjectStreamField(clFields[i], false, true));
}
}
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
Isso é explicado nos comentários: Modifier.STATIC | Modifier.TRANSIENT
campos estáticos e temporários não serão serializados
4. Qual é a função do ID serializado
Não sei se os amigos descobriram que todas as classes que implementam a interface serializável terão a seguinte linha de código
private static final long serialVersionUID = 5791892247841471821L
serialVersionUID: o significado do ID de serialização
Então, qual é a função deste ID de serialização?
Na verdade, é para garantir que a desserialização possa ser bem-sucedida.
Se você não definir o ID de serialização após a implementação da interface de serialização, o java irá gerar um ID de serialização para você (gerado de acordo com o número de tipos de atributos de campo),
mas se você está serializando Após a conversão, adicionar um determinado campo reportará um erro.
Vamos verificá-lo:
//准备两个字段
private String name;
private int age;
//提供setter、getter方法
Teste:
serialização
//初始化
Student student = new Student();
student.setName("张三");
student.setAge(12);
System.out.println(student.toString());
//序列化
try {
//把对象写入文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\imag\\b.txt"));
oos.writeObject(student);
System.out.println("序列化成功!");
} catch (IOException e) {
e.printStackTrace();
}
//反序列化
try {
//从文件中读出对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\imag\\b.txt"));
Student stu = (Student) ois.readObject();
System.out.println("反序列化成功!");
System.out.println(stu.toString());
} catch (Exception e) {
e.printStackTrace();
}
Resultado da impressão:
Aluno {name = '张三', idade = 12}
Serializado com sucesso!
Deserializado com sucesso!
Aluno {name = '张三', idade = 12}
Modifique age
o tipo de dados do campo para o tipo String. Para
este teste, vamos serializá-lo primeiro.
//初始化
Student student = new Student();
student.setName("张三");
student.setAge(12);
System.out.println(student.toString());
//序列化
try {
//把对象写入文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\imag\\b.txt"));
oos.writeObject(student);
System.out.println("序列化成功!");
} catch (IOException e) {
e.printStackTrace();
}
Em seguida, modifique o campo da classe de entidade para o tipo String, comente o código de serialização, execute a desserialização
e relate um erro:
java.io.InvalidClassException: com.li.pojo.Student; local class incompatible: stream classdesc serialVersionUID = -5791892247841471821, local class serialVersionUID = -3882745368779827342
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
Este erro é o mesmo que eu disse no início. O ID de serialização e o ID de desserialização são inconsistentes.
Geralmente, só precisamos implementar a interface de serialização e usar o ID de serialização padrão (1L) para ser
private static final long serialVersionUID = 1L