Java瞬态关键字示例

Java的transient关键字的阶级属性用于/变量来表示这样的类是序列化过程中应该忽略这些变量在创建该类的任何实例持久的字节 Stream 。

甲瞬态变量是无法序列的变量。根据Java语言规范 [ jls-8.3.1.3 ] –“变量可以标记为瞬态,以表明它们不是对象持久状态的一部分。”

在这篇文章中,我将讨论在序列化上下文中与使用瞬态关键字有关的各种概念。

目录

1. Java中的transient关键字是什么?
2. 什么时候应该在Java中使用transient关键字?
3. 带有最终关键字的瞬态用法 
4. 案例研究:HashMap如何使用瞬态?
5. 总结说明

1.什么是Java瞬态关键字

可以将Java中的修饰符瞬变应用于类的字段成员,以关闭这些字段成员上的序列化。标记为瞬态的每个字段都不会被序列化。您可以使用transient关键字向java虚拟机指示transient变量不是对象持久状态的一部分。

让我们写一个非常基本的例子来理解类比的确切含义。我将创建一个Employee类,并且将定义3个属性即的firstNamelastName的confidentialInfo。我们不希望出于某种目的存储/保存“ ConfidentialityInfo ”,因此我们会将字段标记为“ transient ”。

class Employee implements Serializable
{
   private String           firstName;
   private String           lastName;
   private transient String confidentialInfo;

   //Setters and Getters
}

现在让我们序列化一个Employee的实例

try
{
   ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("empInfo.ser"));
   Employee emp = new Employee();
   emp.setFirstName("Lokesh");
   emp.setLastName("Gupta");
   emp.setConfidentialInfo("password");
   //Serialize the object
   oos.writeObject(emp);
   oos.close();
} catch (Exception e)
{
   System.out.println(e);
}

现在,让我们反序列化回java对象,并验证是否保存了“ ConfidentialInfo ”?

try
{
   ObjectInputStream ooi = new ObjectInputStream(new FileInputStream("empInfo.ser"));
   //Read the object back
   Employee readEmpInfo = (Employee) ooi.readObject();
   System.out.println(readEmpInfo.getFirstName());
   System.out.println(readEmpInfo.getLastName());
   System.out.println(readEmpInfo.getConfidentialInfo());
   ooi.close();
} catch (Exception e)
{
   System.out.println(e);
}

程序输出。

Lokesh
Gupta
null

显然,“ 保密信息 ”在序列化时没有保存到持久状态,这正是我们在Java中使用“ transient ”关键字的原因。

2.什么时候应该在Java中使用transient关键字?

现在我们已经非常了解“ transient ”关键字。让我们通过确定需要使用transiental关键字的情况来扩展理解。

  1. 第一种也是非常合乎逻辑的情况是,您可能具有类实例中的其他字段派生/计算的字段。应该每次都以编程方式计算它们,而不是通过序列化保持状态。一个例子可以是基于时间戳的值。例如一个人的年龄或时间戳与当前时间戳之间的持续时间。在这两种情况下,您都将基于当前系统时间而不是实例被序列化时计算变量的值。
  2. 第二个逻辑示例可以是任何安全信息,这些信息不应以任何形式(在数据库或字节 Stream 中)泄漏到JVM外部。
  3. 另一个示例可能是在JDK或应用程序代码中未标记为“可序列化”的字段。未实现Serializable接口且在任何可序列化类中引用的类不能被序列化;并会抛出“ java.io.NotSerializableException”异常。在序列化主类之前,应将这些不可序列化的引用标记为“瞬态”。
  4. 最后,有时候序列化某些字段根本没有意义。期。例如,在任何类中,如果您添加了记录器引用,那么序列化该记录器实例的用途是什么。绝对没有用。您可以从逻辑上序列化表示实例状态的信息。Loggers从不共享实例的状态。它们只是用于编程/调试目的的实用程序。类似的例子可以参考一个Thread类。线程表示在任何给定时间点的进程状态,并且没有用实例存储线程状态的方法。仅仅因为它们不构成您的类实例的状态。

以上四个用例是您应该在参考变量中使用关键字“ transient ”的情况。如果您在逻辑上可以使用“ 瞬态 ”,则可以使用“ 瞬态 ”。请与我分享,我将在列表中更新此内容,以便每个人都可以从您的知识中受益。

阅读更多:实现可序列化接口的最低指南

3.最终过渡

我谈论的是将final关键字与瞬态一起使用,特别是因为它在不同情况下的行为不同,而Java中的其他关键字通常不是这种情况。

为了使该概念切实可行,我对Employee类进行了如下修改:

private String           firstName;
private String           lastName;
//final field 1
public final transient String confidentialInfo = "password";
//final field 2
public final transient Logger logger = Logger.getLogger("demo");

现在,当我再次运行序列化(写/读)时,输出如下:

程序输出。

Lokesh
Gupta
password
null

这很奇怪。我们已将“ secretInfo ” 标记为瞬态;仍然将该字段序列化。对于类似的声明,记录器未序列化。为什么?

原因是,每当任何最终字段/引用被评估为“ 常量表达式 ”时,JVM都会将其序列化,而忽略瞬态关键字的存在。

在上面的示例中,值“ password”是一个常量表达式,而记录器“ demo”的实例是引用。因此,按照规则,secretInfo会保留在logger所没有的地方。

您是否在想,如果我transient在两个字段中都删除“ ”怎么办?好吧,那么实现Serializable引用的字段将持久存在,否则不会持久。因此,如果您在上述代码中删除了瞬态,则String(实现Serializable)将被保留;Logger(未实现Serializable)不会被持久保存,并且将抛出“ java.io.NotSerializableException”。

如果要保留不可序列化字段的状态,请使用readObject()和writeObject()方法。通常在内部将writeObject()/ readObject()链接到序列化/反序列化机制中,从而自动进行调用。

阅读更多:Java中的SerialVersionUID和相关的快速知识

4.案例研究:HashMap如何使用瞬态关键字?

到目前为止,我们一直在讨论与“瞬态”关键字相关的概念,这些概念本质上大多是理论上的。让我们了解在逻辑上内部使用的“ transient ” 的正确用法。它将使您更好地了解java中瞬时关键字的实际用法HashMap

在了解使用瞬态创建的解决方案之前,让我们首先确定问题本身。

HashMap用来存储键值对,我们都知道。而且我们还知道,密钥内部的位置HashMap是根据为密钥实例获得的哈希码计算得出的。现在,当我们序列化一个时HashMap,这意味着内部的所有键HashMap以及与该键对应的所有值也将被序列化。序列化后,当我们反序列化HashMap实例时,所有关键实例也将被反序列化。我们知道在此序列化/反序列化过程中,可能会丢失信息(用于计算哈希码),并且最重要的是它本身是NEW INSTANCE。

在Java中,任何两个实例(即使是相同的类)也不能具有相同的hashcode。这是一个大问题,因为应该根据新的哈希码放置密钥的位置不在正确的位置。检索键的值时,您将在此新HashMap中引用错误的索引。

阅读更多:在Java中使用hashCode和equals方法

因此,当哈希映射序列化时,这意味着哈希索引以及表的顺序不再有效,因此不应保留。这是问题陈述。

现在看看如何在HashMap课堂上解决它。如果遍历HashMap.java的源代码,则会发现以下声明:

transient Entry           table[];
transient int             size;
transient int             modCount;
transient int             hashSeed;
private transient Set     entrySet;

所有重要字段都标记为“ transient ”(它们都是在运行时实际计算/更改的),因此它们不是序列化HashMap实例的一部分。为了再次填充此重要信息,HashMap类使用了writeObject()readObject()方法,如下所示:

private void writeObject(ObjectOutputStream objectoutputstream) throws IOException
{
  objectoutputstream.defaultWriteObject();

  if (table == EMPTY_TABLE)
    objectoutputstream.writeInt(roundUpToPowerOf2(threshold));
  else
    objectoutputstream.writeInt(table.length);

  objectoutputstream.writeInt(size);

  if (size > 0)
  {
    Map.Entry entry;
    for (Iterator iterator = entrySet0().iterator(); iterator.hasNext(); objectoutputstream.writeObject(entry.getValue()))
    {
      entry = (Map.Entry) iterator.next();
      objectoutputstream.writeObject(entry.getKey());
    }
  }
}

private void readObject(ObjectInputStream objectinputstream) throws IOException, ClassNotFoundException
{
  objectinputstream.defaultReadObject();

  if (loadFactor <= 0.0F || Float.isNaN(loadFactor))
    throw new InvalidObjectException((new StringBuilder())
    .append("Illegal load factor: ").append(loadFactor).toString());

  table = (Entry[]) EMPTY_TABLE;
  objectinputstream.readInt();

  int i = objectinputstream.readInt();
  if (i < 0)
    throw new InvalidObjectException((new StringBuilder()).append("Illegal mappings count: ").append(i).toString());
  int j = (int) Math.min((float) i * Math.min(1.0F / loadFactor, 4F), 1.073742E+009F);
  if (i > 0)
    inflateTable(j);
  else
    threshold = j;

  init();

  for (int k = 0; k < i; k++)
  {
    Object obj = objectinputstream.readObject();
    Object obj1 = objectinputstream.readObject();
    putForCreate(obj, obj1);
  }
}

使用上面的代码,HashMap仍然让非瞬态字段像往常一样被处理,但是它们将存储的键值对一个接一个地写在字节数组的末尾。反序列化时,它让非临时变量通过默认的反序列化过程进行处理,然后逐个读取键值对。对于每个键,将再次计算哈希和索引,并将其插入到表中的正确位置,以便可以再次检索它而没有任何错误。

上面使用瞬时关键字是正确使用案例的一个很好的例子。您应该记住它,并在下一个Java面试问题中问到时提起它。

相关文章:HashMap如何在Java中工作?

5.总结说明

  1. 修饰符瞬态可以应用于类的字段成员,以关闭这些字段成员上的序列化。
  2. 您可以在具有需要在现有状态字段上进行保护或计算的字段的类中使用瞬态关键字。并在根本不需要序列化记录器和线程等字段的情况下使用它。
  3. 序列化不关心访问修饰符,例如private;所有非瞬态字段都被视为对象持久状态的一部分,并且有资格进行持久化。
  4. 每当任何最终字段/引用被评估为“常量表达式”时,JVM都会将其序列化,而忽略瞬态关键字的存在。
  5. HashMap类是Java中瞬时关键字的一个好用例。

就我而言,这就是“ transient ”关键字的全部。如果您想在这篇文章中添加一些内容,请通过评论告知我。我很乐意扩大这篇文章。

如果您以后想了解更多类似的概念,建议您加入我的邮件列表,或者在Baidu Plus / Facebook或Twitter上关注我。我的社交资料中确实张贴了除了how2codex.com以外的有趣链接。

saigon has written 1440 articles

Leave a Reply