Вы достигли нового уровня

Уровень 20

1. Нужно сохранить объект в файл - построчно. Сохранение массива объектов

- Привет, Амиго! Сегодня мы познакомимся с еще одной интересной темой. А именно: сохранением и загрузкой (восстановлением) объектов. Допустим у нас есть класс Cat:

Код
class Cat
{
 public String name;
 public int age;
 public int weight;
}

И мы хотим добавить в него удобный механизм сохранения в файл и загрузки из файла.

Вот как это можно сделать:

Код
class Cat
{
 public String name;
 public int age;
 public int weight;

 public void save(OutputStream outputStream) throws Exception
 {
  PrintWriter printWriter = new PrintWriter(outputStream);
  printWriter.println(name);
  printWriter.println(age);
  printWriter.println(weight);
 }

 public void load(InputStream inputStream) throws Exception
 {
  BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
  name = reader.readLine();
  age = Integer.parseInt(reader.readLine());
  weight = Integer.parseInt(reader.readLine());
 }
}

- О! Это же очень просто. Мы просто печатаем значения всех аргументов, по одному в каждой строчке. А при загрузке читаем их в том же порядке. Отличное решение.

- Спасибо, Амиго. А можешь написать, как будут выглядеть методы save и load у такой группы классов:

Код
class Cat
{
 public String name;
 public int age;
 public int weight;
}
class Dog
{
 public String name;
 public int age;
}
class Human
{
 public Cat cat;
 public Dog dog;
}

У тебя есть объект человек, и он может иметь одну собаку и одного кота.

- У меня есть решение:

Код
class Cat
{
 public String name;
 public int age;
 public int weight;

 public void save(OutputStream outputStream) throws Exception
 {
  PrintWriter printWriter = new PrintWriter(outputStream);
  printWriter.println(name);
  printWriter.println(age);
  printWriter.println(weight);
 }

 public void load(InputStream inputStream) throws Exception
 {
  BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
  name = reader.readLine();
  age = Integer.parseInt(reader.readLine());
  weight = Integer.parseInt(reader.readLine());
 }
}
class Dog
{
 public String name;
 public int age;

 public void save(OutputStream outputStream) throws Exception
 {
  PrintWriter printWriter = new PrintWriter(outputStream);
  printWriter.println(name);
  printWriter.println(age);
 }

 public void load(InputStream inputStream) throws Exception
 {
  BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
  name = reader.readLine();
  age = Integer.parseInt(reader.readLine());
 }
}
class Human
{
 public Cat cat;
 public Dog dog;

 public void save(OutputStream outputStream) throws Exception
 {
  cat.save(outputStream);
  dog.save(outputStream);
 }

 public void load(InputStream inputStream) throws Exception
 {
  cat.load(inputStream);
  dog.load(inputStream);
 }
}

- Очень хорошее решение. Но что будет, если у человека нет кота, а есть только собака?

Где проверки на null?

- Сейчас исправлю:

Код
class Human
{
 public Cat cat;
 public Dog dog;

 public void save(OutputStream outputStream) throws Exception
 {
  if (cat != null)
   cat.save(outputStream);
  if (dog != null)
   dog.save(outputStream);
 }

 public void load(InputStream inputStream) throws Exception
 {
  cat = new Cat();
  cat.load(inputStream);
  dog = new Dog();
  dog.load(inputStream);
 }
}

- Все равно не очень верно. У тебя две ошибки:

1) Если у человека нет ни кота, ни собаки, они все равно будут созданы, при вызове метода load

2) Если мы сохраним только собаку, то ее данные будут прочитаны котом при загрузке.

- А что же делать?

- Мы не можем пропускать запись переменных, иначе это вызовет сбой при чтении. Но нужно сделать так, чтобы переменные, чье значение было null при сохранении и после загрузки, получали null. Вот мой вариант:

Код
class Human
{
 public Cat cat;
 public Dog dog;

 public void save(OutputStream outputStream) throws Exception
 {
  PrintWriter writer = new PrintWriter(outputStream);

  String isCatPresent = cat != null ? "yes" : "no";
   writer.print(isCatPresent);

   if (cat!=null)
    cat.save(outputStream);

   String isDogPresent = dog != null ? "yes" : "no";
    writer.print(isDogPresent);

    if (dog != null)
     dog.save(outputStream);
}

public void load(InputStream inputStream) throws Exception
{
 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

  String isCatPresent = reader.readLine();
  if (isCatPresent.equals("yes"))
  {
   cat = new Cat();
   cat.load(inputStream);
  }

  String isDogPresent = reader.readLine();
  if (isDogPresent.equals("yes"))
  {
   dog = new Dog();
   dog.load(inputStream);
  }
 }
}

- Да, мне нравится такое решение.

- Да, что-то в нем есть.

2. Задача на сохранение массива объектов в файл

- Привет, Амиго!

Задачи
1. Читаем и пишем в файл: Human

Реализуйте логику записи в файл и чтения из файла для класса Human
Поле name в классе Human не может быть пустым
В файле your_file_name.tmp может быть несколько объектов Human
Метод main реализован только для вас и не участвует в тестировании
2. Читаем и пишем в файл: JavaRush

Реализуйте логику записи в файл и чтения из файла для класса JavaRush
В файле your_file_name.tmp может быть несколько объектов JavaRush
Метод main реализован только для вас и не участвует в тестировании
3. Знакомство с properties

В методе fillInPropertiesMap считайте имя файла с консоли и заполните карту properties данными из файла. Про .properties почитать тут - http://ru.wikipedia.org/wiki/.properties Реализуйте логику записи в файл и чтения из файла для карты properties.
4. Читаем и пишем в файл статики

Реализуйте логику записи в файл и чтения из файла для класса ClassWithStatic
Метод load должен инициализировать объект включая статические поля данными из файла
Метод main реализован только для вас и не участвует в тестировании
5. И еще раз о синхронизации

Разберитесь почему не работает метод main()
Реализуйте логику записи в файл и чтения из файла для класса Object
Метод load должен инициализировать объект данными из файла
Метод main реализован только для вас и не участвует в тестировании

3. Решение - сериализация, ISerializable

- Помнишь, мы сегодня разбирали сохранение объектов в файл и чтение из файла?

- Да, только мы сохраняли в поток вывода, а читали из потока ввода.

- Молодец, Амиго. Приятно слышать, что ты замечаешь такие мелочи. А ты бы смог дописать код, чтобы было сохранение в файл и чтение из файла?

- А что там писать?! Объявил FileInputStream и FileOutputStream и передавай их в методы save & load. Тут уже ничего не перепутаешь - все просто.

- Рада за тебя. Итак, новая тема – сериализация.

Сериализация – это практически то же самое, что мы с тобой только что делали, только гораздо круче и встроено прямо в Java-машину. Java-машина умеет сохранять и загружать свои объекты. Для этого ей даже не требуются методы save & load: все объекты хранятся внутри Java-машины, и она имеет к ним полный доступ.

Мы просто берем объект и сохраняем его в поток/читаем из потока:

Код
public static void main(String[] args) throws Exception
{
 Cat cat = new Cat();

 //save cat to file
 FileOutputStream fileOutput = new FileOutputStream("cat.dat");
 ObjectOutputStream outputStream = new ObjectOutputStream(fileOutput);
 outputStream.writeObject(cat);
 fileOutput.close();
 outputStream.close();

 //load cat from file
 FileInputStream fiStream = new FileInputStream("cat.dat");
 ObjectInputStream objectStream = new ObjectInputStream(fiStream);
 Object object = objectStream.readObject();
 fiStream.close();
 objectStream.close();

 Cat newCat = (Cat)object;
}

- И все?

- Да. Там очень большой и сложный механизм сериализации, который поддерживает сохранение в поток и чтение из потока почти всех типов данных.

- Почти всех, это значит не всех?

- Да, дело в том, что не все объекты по своей сути можно сохранить. Некоторые объекты не хранят все свои данные в себе, а лишь ссылаются на другие объекты и/или источники данных. Например, консоль (System.in), поток ввода (InputStream), или что-нибудь еще.

Поэтому разработчики Java придумали специальный интерфейс-маркерSerializable. Его называют маркером, т.к. он не содержит никаких данных и методов. Он используется только для того, чтобы «помечать» (маркировать) классы. Если мы считаем, что наш класс хранит в себе все свои данные, тогда мы можем пометить его этим маркером – написать implements Serializable.

Пример «кота» с поддержкой сериализации:

Код
class Cat implements Serializable
{
 public String name;
 public int age;
 public int weight;
}

Когда мы пытаемся сериализовать (сохранить) какой-нибудь объект, Java-машина проверяет – поддерживает ли он сериализацию: реализует ли он интерфейс Serializable? Если да, то сохраняет объект, если нет – выкидывает исключение о невозможности сериализации.

Тут нужно понимать, что сериализуемый объект должен состоять тоже только из сериализуемых объектов.

- Ну, этого и следовало ожидать. Нельзя же сохранить целое, не сохранив какие-то его части.

- Именно так.

- А как же типы int, String, ArrayList?

- Они все поддерживают сериализацию, на этот счет разработчики Java специально позаботились. Тут проблем быть не должно.

Более того, при сериализации объекта сохраняется его тип. Теперь ты можешь в переменную класса с типом Object сохранить ссылку на объект Cat, и все это отлично сериализуется и десериализуется.

- Десериализуется?

- Десериализация – так называют процесс, обратный сериализации – чтение и восстановление объекта из потока/файла.

- Тогда вопросов больше нет.

4. Задачи на сериализацию

- Привет, Амиго!

Задачи
1. Как сериализовать?

Сделайте так, чтобы сериализация класса Human была возможной
2. Как сериализовать JavaRush?

Сделайте так, чтобы сериализация класса JavaRush была возможной
3. Как сериализовать Singleton?

Два десериализованных объекта singleton и singleton1 имеют разные ссылки в памяти, а должны иметь одинаковые.
В класс Singleton добавьте один метод (погуглите), чтобы после десериализации ссылки на объекты были равны.
Метод main не участвует в тестировании.
4. Как сериализовать static?

Сделайте так, чтобы сериализация класса ClassWithStatic была возможной
5. Как сериализовать что-то свое?

Сделайте так, чтобы сериализация класса Object была возможной

5. transient

- Привет, Амиго! Хотела тебе порассказывать одно маленькое дополнение к сериализации.

Допустим наш класс содержит ссылку на какой-нибудь InputStream, тогда его нельзя сериализовать, ведь так?

- Да. Ты же сама говорила, что потоки сериализовать нельзя. А сериализовать объект, у которого есть несериализуемые данные – тоже нельзя.

- Да. Именно так. Но что, если класс хранит данные, которые не играют значащей роли в его состоянии, но мешают считаться ему сериализуемым классом? Мало ли что класс может у себя хранить ненужного. Возможно, он может выбросить эти данные в любой момент или даже так и делает постоянно.

Для таких случаев разработчики Java придумали специальное слово – transient. Его можно написать перед переменной класса и она будет не учитываться при сериализации. Ее состояние не будет ни сохраняться, ни восстанавливаться. Как будто и нет ее вовсе. Как раз для таких ситуаций, как мы только что рассмотрели.

Помнишь кеширование и модификатор volatile? Нет правил без исключений.

Вот тебе один примерчик такого счастья:

Пример «кота» с невидимой для сериализации переменной - in:

Код
class Cat implements Serializable
{
 public String name;
 public int age;
 public int weight;

 transient public InputStream in = System.in;
}

6. Сохранение данных/externalizable

- Привет, Амиго! Хотел бы немного дополнить то, что рассказала тебе Элли.

Иногда бывает нужно управлять процессом сериализации. Вот одни из причин:

1) Объект не готов к сериализации: его нынешнее внутреннее состояние в процессе изменения.

2) Объект содержит несериализуемые объекты, но может перевести их в удобную для сериализации форму: сохранить в массив байт или что-нибудь еще.

3) Объект хочет десериализовать все свои данные, как одно целое и/или зашифровать их перед сериализацией.

Причин выполнить сериализацию в ручном режиме может быть множество. Но не хотелось бы лишаться всех тех преимуществ, которые дает стандартная сериализация. Ведь наш объект могут использовать другие объекты. И они тоже не смогут сериализоваться, если наш объект откажется от поддержки сериализации.

На этот случай тоже есть решение – интерфейс Externalizable. Спасибо дальновидным разработчикам Java. Достаточно заменить интерфейс Serializable на интерфейс Externalizable, и ваш класс сможет управлять процессом сериализации в ручном режиме.

Дело в том, что интерфейс Externalizable, в отличие от Serializable, содержит два метода, которые вызываются Java-машиной при сериализации объекта. Вот как это выглядит:

Код
class Cat implements Externalizable
{
 public String name;
 public int age;
 public int weight;

 public void writeExternal(ObjectOutput out)
 {
  out.writeObject(name);
  out.writeInt(age);
  out.writeInt(weight);
}

 public void readExternal(ObjectInput in)
 {
  name = (String) in.readObject();
  age = in.readInt();
  weight = in.readInt();
 }
}

Ничего не напоминает?

- Ух ты! Именно так мы пробовали сохранять объекты до «изобретения» сериализации.

- Да, теперь все просто: если устраивает стандартная сериализация – просто наследуем наш класс от интерфейса Serializable. Если не устраивает – наследуем от Externalizable и пишем код для сохранения/загрузки объекта нашего класса.

- А класс, помеченный Externalizable, считается сериализуемым? Мы можем «безопасно» хранить на него ссылки в наших сериализуемых классах?

- Да. Если класс реализует интерфейс Serializable или Externalizable, он считается сериализуемым.

- Отличное решение. Мне нравится.

- Рад это слышать. Но это еще не всё… Спроси лучше профессора Ханса о всяких нюансах. Они тут точно есть. Он хотел тебе дать что-то почитать.

7. Задачи на сериализацию и десериализацию

- Привет, Амиго!

Задачи
1. Externalizable для апартаментов

Реализуйте интерфейс Externalizable для класса Apartment
Подумайте, какие поля не нужно сериализовать.
2. OutputToConsole

Класс OutputToConsole должен сериализоваться с помощью интерфейса Externalizable.
Подумайте, какие поля не нужно сериализовать.
Исправьте ошибку.
Сигнатуры методов менять нельзя.
3. Externalizable Person

Класс Person должен сериализоваться с помощью интерфейса Externalizable.
Подумайте, какие поля не нужно сериализовать.
Исправьте ошибку сериализации.
Сигнатуры методов менять нельзя.
4. Serializable Solution

Сериализуйте класс Solution.
Подумайте, какие поля не нужно сериализовать, пометить ненужные поля — transient.
Объект всегда должен содержать актуальные на сегодняшний день данные.
Метод main не участвует в тестировании.
Написать код проверки самостоятельно в методе main:
1) создать файл, открыть поток на чтение (input stream) и на запись(output stream)
2) создать экземпляр класса Solution - savedObject
3) записать в поток на запись savedObject (убедитесь, что они там действительно есть)
4) создать другой экземпляр класса Solution с другим параметром
5) загрузить из потока на чтение объект - loadedObject
6) проверить, что savedObject.string равна loadedObject.string
7) обработать исключения
5. Переопределение сериализации

Сделайте так, чтобы после десериализации нить runner продолжила работать.
Ключевые слова объекта runner менять нельзя.

Hint/Подсказка: Конструктор не вызывается при сериализации, только инициализируются все поля.

8. Ссылка на сериализацию

- А если ты хочешь расширить или углубить свои знания по сериализации. Или, не дай бог, что-нибудь не понял, то для тебя у меня есть замечательные лекции.

Ссылка на сериализацию

9. Хулио

- Привет, Амиго! Устал? Ничего, тяжело в учении – легко в бою. Давай посмотрим хорошее видео.

Оригинал видео на YouTube

10. Домашние и бонусные задания

- Привет, Амиго! Что-то ты расслабился! Вот твои задания. Если вдруг возникнут трудности, то обращайся к коллегам, они точно помогут.

Дополнительные задания для выполнения в Intellij Idea
1. Минимум изменений

Используя минимум изменений кода сделайте так, чтобы сериализация класса C стала возможной.
2. Десериализация

На вход подается поток, в который записан сериализованный объект класса A либо класса B.
Десериализуйте объект в методе getOriginalObject предварительно определив, какого именно типа там объект.
Реализуйте интерфейс Serializable где необходимо.
3. Найти ошибки

Почему-то при сериализации/десериализации объекта класса B возникают ошибки.
Найдите проблему и исправьте ее.
Класс A не должен реализовывать интерфейсы Serializable и Externalizable.
Сигнатура класса В не содержит ошибку :)
Метод main не участвует в тестировании.
4. Исправить ошибку

После десериализации объекта класса Solution обнаружили, что данных в словаре [m] нет :(
Исправить 1 ошибку.
Метод main в тестировании не участвует.
5. Сериализуйте Person

Сериализуйте класс Person стандартным способом.
При необходимости поставьте полям модификатор transient.
6. Запрет сериализации

Запретите сериализацию класса SubSolution используя NotSerializableException.
Сигнатуры классов менять нельзя
7. Переопределение сериализации в потоке

Сериализация/десериализация Solution не работает.
Исправьте ошибки не меняя сигнатуры методов и класса.
Метод main не участвует в тестировании.
Написать код проверки самостоятельно в методе main:
1) создать экземпляр класса Solution
2) записать в него данные - writeObject
3) сериализовать класс Solution - writeObject(ObjectOutputStream out)
4) десериализовать, получаем новый объект
5) записать в новый объект данные - writeObject
6) проверить, что в файле есть данные из п.2 и п.5
8. Правильный вывод

Расставить обращение к методам суперкласса и модификаторы доступа так, чтобы вывод на экран был следующим:

C class, method2
A class, method2
A class, method1
B class, method1

Количество изменений модификаторов доступа и вызовов методов должно быть минимально
1. Из одного метода можно вызвать только один метод суперкласса.
2. Из одного метода можно вызвать только один метод класса.
3. Можно менять модификаторы доступа к методам.
9. Знакомство с графами

Прочитать в дополнительных материалах о сериализации графов.
Дан ориентированный плоский граф Solution, содержащий циклы и петли.
Пример,
Сериализовать Solution.
Все данные должны сохранить порядок следования.

- Ты уже всё сделал? Вот тебе ещё и бонусные задания повышенной сложности:

1. Алгоритмы-числа

Задача: Число S состоит из M чисел, например, S=370 и M(количество цифр)=3
Реализовать логику метода getNumbers, который должен среди натуральных чисел меньше N (long) находить все числа, удовлетворяющие следующему критерию:
число S равно сумме его цифр, возведенных в M степень

getNumbers должен возвращать все такие числа в порядке возрастания

Пример искомого числа:
370 = 3*3*3 + 7*7*7 + 0*0*0
8208 = 8*8*8*8 + 2*2*2*2 + 0*0*0*0 + 8*8*8*8

На выполнение дается 10 секунд и 50 МБ памяти.
2. Алгоритмы-прямоугольники

Задача: 1. Дан двумерный массив N*N, который содержит несколько прямоугольников.
2. Различные прямоугольники не соприкасаются и не накладываются.
3. Внутри прямоугольник весь заполнен 1.
4. В массиве:
4.1) a[i, j] = 1, если элемент (i, j) принадлежит какому-либо прямоугольнику
4.2) a[i, j] = 0, в противном случае
5. getRectangleCount должен возвращать количество прямоугольников.
6. Метод main не участвует в тестировании
3. Кроссворд

Задача: 1. Дан двумерный массив, который содержит буквы английского алфавита в нижнем регистре.
2. Метод detectAllWords должен найти все слова из words в массиве crossword.
3. Элемент(startX, startY) должен соответствовать первой букве слова, элемент(endX, endY) - последней.
text - это само слово, располагается между начальным и конечным элементами
4. Все слова есть в массиве.
5. Слова могут быть расположены горизонтально, вертикально и по диагонали как в нормальном, так и в обратном порядке.
6. Метод main не участвует в тестировании
4. Свой список

Задача: Посмотреть, как реализован LinkedList.
Элементы следуют так: 1->2->3->4 и так 4->3->2->1
По образу и подобию создать Solution.
Элементы должны следовать так:
1->3->7->15
    ->8...
 ->4->9
    ->10
2->5->11
    ->12
 ->6->13
    ->14
Во внутренней реализации элементы должны добавляться по 2 на каждый уровень
Метод getParent должен возвращать элемент, который на него ссылается.
Например, 3 ссылается на 7 и на 8, т.е. getParent("8")=="3", а getParent("13")=="6"
Строки могут быть любыми.
При удалении элемента должна удаляться вся ветка. Например, list.remove("5") должен удалить "5", "11", "12"
Итерироваться элементы должны в порядке добавления
Доступ по индексу запрещен, воспользуйтесь при необходимости UnsupportedOperationException
Должно быть наследование AbstractList<String>, List<String>, Cloneable, Serializable
Метод main в тестировании не участвует