Ключевое слово Java transient используется в атрибутах/переменных класса, чтобы указать, что процесс сериализации такого класса должен игнорировать такие переменные при создании постоянного потока байтов для любого экземпляра этого класса.
Переходная переменная — это переменная, которая не может быть сериализована. Согласно спецификации языка Java [ jls-8.3.1.3 ] — «Переменные могут быть помечены как переходные, чтобы указать, что они не являются частью постоянного состояния объекта».
В этой статье я рассмотрю различные концепции, связанные с использованием ключевого слова transient в контексте сериализации.
1. Что такое ключевое слово Java transient?
Модификатор transient в java может быть применен к членам поля класса для отключения сериализации этих членов поля. Каждое поле, помеченное как transient, не будет сериализовано. Ключевое слово transient используется для указания виртуальной машине java, что переменная transient не является частью постоянного состояния объекта.
Давайте напишем очень простой пример, чтобы понять, что именно означает приведенная выше аналогия. Я создам класс Employee и определю 3 атрибута, а именно firstName, lastName и confidencialInfo. Мы не хотим хранить/сохранять «confidentialInfo» для какой-либо цели, поэтому мы отметим поле как «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 objectoos.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 backEmployee 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);}
Вывод программы.
LokeshGuptanull
Очевидно, что «confidentialInfo» не была сохранена в постоянном состоянии во время сериализации, и именно поэтому мы используем ключевое слово «transient» в Java.
2. Когда следует использовать ключевое слово transient в Java?
Теперь у нас есть очень хорошее знание ключевого слова «transient». Давайте расширим наше понимание, определив ситуации, в которых вам понадобится использование ключевого слова transient.
- Первый и очень логичный случай — когда у вас могут быть поля, которые выводятся/вычисляются из других полей в экземпляре класса. Они должны вычисляться программно каждый раз, а не сохранять состояние посредством сериализации. Примером может быть значение на основе временной метки; например, возраст человека ИЛИ длительность между временной меткой и текущей временной меткой. В обоих случаях вы будете вычислять значение переменной на основе текущего системного времени, а не времени сериализации экземпляра.
- Вторым логическим примером может быть любая защищенная информация, которая не должна выходить за пределы JVM ни в какой форме(ни в базе данных, ни в потоке байтов).
- Другим примером могут быть поля, которые не помечены как «Serializable» внутри JDK или кода приложения. Классы, которые не реализуют интерфейс Serializable и на которые ссылаются в любом сериализуемом классе, не могут быть сериализованы; и будут выдавать исключение «java.io.NotSerializableException». Эти несериализуемые ссылки должны быть помечены как «transient» перед сериализацией основного класса.
- И, наконец, бывают случаи, когда просто не имеет смысла сериализовать некоторые поля. Точка. Например, в любом классе, если вы добавили ссылку на регистратор, то какой смысл сериализовать этот экземпляр регистратора. Абсолютно бесполезен. Вы сериализуете информацию, которая представляет состояние экземпляра, логически. Регистраторы никогда не разделяют состояние экземпляра. Они просто утилиты для целей программирования/отладки. Подобным примером может быть ссылка на класс Thread. Потоки представляют состояние процесса в любой заданный момент времени, и нет смысла хранить состояние потока в вашем экземпляре; просто потому, что они не составляют состояние экземпляра вашего класса.
Вышеуказанные четыре варианта использования — это случаи, когда вам следует использовать ключевое слово «transient» с ссылочными переменными. Если у вас есть более логичные случаи, где можно использовать «transient», пожалуйста, поделитесь со мной, и я обновлю это здесь в списке, чтобы все могли извлечь пользу из ваших знаний.
Подробнее: Минимальное руководство по реализации сериализуемого интерфейса
3. Переходный с финалом
Я говорю об использовании transient с ключевым словом final именно потому, что оно ведет себя по-разному в разных ситуациях, что обычно не относится к другим ключевым словам в Java.
Чтобы сделать эту концепцию практичной, я изменил класс Employee следующим образом:
private String firstName;private String lastName;//final field 1public final transient String confidentialInfo = "password";//final field 2public final transient Logger logger = Logger.getLogger("demo");
Теперь, когда я снова запускаю сериализацию(запись/чтение), ниже приведен вывод:
Вывод программы.
LokeshGuptapasswordnull
Это странно. Мы пометили «confidentialInfo» как transient; и все равно поле было сериализовано. Для аналогичного объявления logger не был сериализован. Почему?
Причина в том, что всякий раз, когда какое-либо конечное поле/ссылка оценивается как « константное выражение », оно сериализуется JVM, игнорируя наличие ключевого слова transient.
В приведенном выше примере значение «password» является константным выражением, а экземпляр логгера «demo» является ссылкой. Таким образом, по правилу, privacyInfo сохранялась, а логгер — нет.
Вы думаете, что если я удалю «transient» из обоих полей? Ну, тогда поля, реализующие ссылки Serializable, будут сохраняться, в противном случае — нет. Таким образом, если вы удалите transient в приведенном выше коде, String(который реализует Serializable) будет сохраняться; тогда как Logger(который НЕ реализует Serializable) не будет сохраняться И будет выдано «java.io.NotSerializableException».
Если вы хотите сохранить состояние несериализуемых полей, используйте методы readObject() и writeObject(). writeObject()/readObject() обычно внутренне связаны с механизмами сериализации/десериализации и поэтому вызываются автоматически.
Подробнее: SerialVersionUID в Java и связанные с ним краткие факты
4. Пример использования: как HashMap использует ключевое слово transient?
До сих пор мы говорили о концепциях, связанных с ключевым словом «transient», которые в основном носят теоретический характер. Давайте разберемся с правильным использованием «transient», которое используется внутри класса HashMap очень логично. Это даст вам лучшее представление о реальном использовании ключевого слова transient в Java.
Прежде чем разобраться в решении, созданном с использованием переходного процесса, давайте сначала определим саму проблему.
HashMap используется для хранения пар ключ-значение, мы все это знаем. И мы также знаем, что расположение ключей внутри HashMap вычисляется на основе хэш-кода, полученного для экземпляра ключа. Теперь, когда мы сериализуем HashMap, это означает, что все ключи внутри HashMap и все значения, соответствующие ключам, также будут сериализованы. После сериализации, когда мы десериализуем экземпляр HashMap, все экземпляры ключей также будут десериализованы. Мы знаем, что во время этого процесса сериализации/десериализации может быть потеря информации(используемой для вычисления хэш-кода), а самое главное, что это сам НОВЫЙ ЭКЗЕМПЛЯР.
В Java любые два экземпляра(даже одного класса) не могут иметь одинаковый хэш-код. Это большая проблема, поскольку места, где должны быть размещены ключи в соответствии с новыми хэш-кодами, не находятся в правильных позициях. При извлечении значения ключа вы будете ссылаться на неправильный индекс в этом новом HashMap.
Подробнее: Работа с методами hashCode и equals в Java
Итак, когда хэш-карта сериализуется, это означает, что хэш-индекс, а следовательно, и порядок таблицы больше недействительны и не должны сохраняться. Это постановка проблемы.
Теперь посмотрим, как это решается внутри класса HashMap. Если просмотреть исходный код HashMap.java, вы найдете следующие объявления:
transient Entry table[];transient int size;transient int modCount;transient int hashSeed;private transient Set entrySet;
Все важные поля были помечены как «переходные»(все они фактически вычисляются/изменяются во время выполнения), поэтому они не являются частью сериализованного экземпляра HashMap. Чтобы снова заполнить эту важную информацию, класс HashMap использует методы writeObject() и readObject(), как показано ниже:
private void writeObject(ObjectOutputStream objectoutputstream) throws IOException{objectoutputstream.defaultWriteObject();if(table == EMPTY_TABLE)objectoutputstream.writeInt(roundUpToPowerOf2(threshold));elseobjectoutputstream.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);elsethreshold = j;init();for(int k = 0; k < i; k++){Object obj = objectinputstream.readObject();Object obj1 = objectinputstream.readObject();putForCreate(obj, obj1);}}
С кодом выше HashMap по-прежнему позволяет обрабатывать непереходные поля так, как они это делают обычно, но они записывают сохраненные пары ключ-значение в конце массива байтов одну за другой. При десериализации он позволяет обрабатывать непереходные переменные процессом десериализации по умолчанию, а затем считывать пары ключ-значение одну за другой. Для каждого ключа хэш и индекс снова вычисляются и вставляются в правильную позицию в таблице, чтобы их можно было извлечь снова без ошибок.
Вышеуказанное использование ключевого слова transient было очень хорошим примером правильного варианта использования. Вы должны помнить его и упоминать его всякий раз, когда его спросят в вашем следующем вопросе на собеседовании по Java.
Связанная запись: Как работает HashMap в Java?
5. Краткие заметки
- Модификатор transient можно применить к членам полей класса, чтобы отключить сериализацию для этих членов полей.
- Вы можете использовать ключевое слово transient в классах с полями, которые необходимо защитить или вычислить на основе существующих полей состояния. И используйте его, когда просто не имеет смысла сериализовать эти поля, такие как логгеры и потоки.
- Сериализация не учитывает модификаторы доступа, такие как private; все непереходные поля считаются частью постоянного состояния объекта и подлежат сохранению.
- Всякий раз, когда какое-либо конечное поле/ссылка оценивается как «константное выражение», оно сериализуется JVM, игнорируя наличие ключевого слова transient.
- Хорошим примером использования ключевого слова transient в Java является класс HashMap.
Это все с моей стороны по ключевому слову «transient». Если вы хотите что-то добавить в этот пост, пожалуйста, дайте мне знать в комментариях. Я с удовольствием дополню этот пост.
Если вы хотите узнать больше о подобных концепциях в будущем, я предлагаю вам присоединиться к моему списку рассылки/ ИЛИ следить за мной в Google Plus/Facebook или Twitter. Я публикую интересные ссылки, помимо howtodoinjava.com, в своих социальных профилях.
Ссылки:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.3
http://www.oracle.com/technetwork/articles/java/javaserial-1536170.html
http://docs.oracle.com/javase/specs/jls/se5.0/html/expressions.html#15.28