Java Serialization 2013 — различия между версиями
Материал из SEWiki
Snurk (обсуждение | вклад) (→1. TestFrame) |
Snurk (обсуждение | вклад) (→1. Serialization.java (основная часть)) |
||
Строка 7: | Строка 7: | ||
<source lang="java"> | <source lang="java"> | ||
+ | import java.io.Externalizable; | ||
+ | import java.io.FileInputStream; | ||
+ | import java.io.FileNotFoundException; | ||
+ | import java.io.FileOutputStream; | ||
+ | import java.io.IOException; | ||
+ | import java.io.InputStream; | ||
+ | import java.io.ObjectInput; | ||
+ | import java.io.ObjectInputStream; | ||
+ | import java.io.ObjectOutput; | ||
+ | import java.io.ObjectOutputStream; | ||
+ | import java.io.ObjectStreamField; | ||
+ | import java.io.OutputStream; | ||
+ | import java.io.Serializable; | ||
+ | import java.util.ArrayList; | ||
+ | import java.util.List; | ||
+ | |||
+ | import org.junit.Test; | ||
+ | |||
+ | |||
+ | |||
+ | /** | ||
+ | * Лекция о сериализации в Java. | ||
+ | */ | ||
+ | @SuppressWarnings({"unused"}) | ||
+ | public class Serialization { | ||
+ | |||
+ | /****Введение****/ | ||
+ | |||
+ | /** | ||
+ | * | ||
+ | * Сериализация -- запись объекта в байтовый поток. | ||
+ | * | ||
+ | * Десереализация -- CO. | ||
+ | * | ||
+ | * Все в пакете java.io | ||
+ | * | ||
+ | * Мотивация: | ||
+ | * 1. dump (ex. JavaBeans persistence) | ||
+ | * 2. RMI | ||
+ | */ | ||
+ | class Point { | ||
+ | double x; | ||
+ | double y; | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | //move to Point1.java and SimplePointTest.java | ||
+ | |||
+ | |||
+ | /****Процесс сериализации****/ | ||
+ | |||
+ | /** | ||
+ | * В сериализации участвуют ВСЕ достижимые объекты. | ||
+ | * | ||
+ | * Запись: | ||
+ | * 1. Запись предка (если он сериализуем) | ||
+ | * 2. Запись полей (если они сериализуемы) | ||
+ | * | ||
+ | * Циклические зависимости корректно обрабатываются автоматически. | ||
+ | * | ||
+ | * Чтение: | ||
+ | * 1. Выделение памяти под объект | ||
+ | * 2. Чтение предка | ||
+ | * 3. Чтение полей | ||
+ | */ | ||
+ | |||
+ | /** | ||
+ | * Все поля должны быть сериализуемы! | ||
+ | */ | ||
+ | @SuppressWarnings("serial") | ||
+ | class Polygon implements Serializable { | ||
+ | |||
+ | List<Point3> vertices = new ArrayList<>(); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Пока все просто, но впечатление обманчиво! | ||
+ | * | ||
+ | * 1. Внутреннее представление становится частью интерфейса | ||
+ | * Инкапсуляция летит к чертям! | ||
+ | * 2. Увеличивает объемы тестирования новых версий | ||
+ | * 3. Дополнительный конструктор! | ||
+ | * Необходимо обеспечивать все инварианты | ||
+ | * Некоторые дыры в безопасности | ||
+ | * 4. Все потомки становятся сериализуемыми! | ||
+ | */ | ||
+ | |||
+ | /****Управление сериализацией****/ | ||
+ | |||
+ | /** | ||
+ | * Serial version UID (stream unique identifier) | ||
+ | * | ||
+ | * Применяется для обеспечения совместимости, когда версии класса изменяются | ||
+ | * | ||
+ | * По-умолчанию автоматически создается на основе имени, интерфейсов, | ||
+ | * public и protected полей | ||
+ | * | ||
+ | * В случае несовпадения -- InvalidClassException | ||
+ | * | ||
+ | */ | ||
+ | class Point2 implements Serializable { | ||
+ | private static final long serialVersionUID = 4889340678034881968L; | ||
+ | |||
+ | double x; | ||
+ | double y; | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Модификатор transient. | ||
+ | * Исключает поле из процесса сериализации. | ||
+ | * Примеры: | ||
+ | * 1. Избыточная информация (предподсчитанные значения) | ||
+ | * 2. Несериализуемые поля | ||
+ | * | ||
+ | */ | ||
+ | class Point3 implements Serializable { | ||
+ | private static final long serialVersionUID = 4889340678034881968L; | ||
+ | |||
+ | double x; | ||
+ | double y; | ||
+ | transient int precomputedHash; | ||
+ | |||
+ | public int hashCode() { | ||
+ | return precomputedHash; | ||
+ | } | ||
+ | |||
+ | private int countHash() { | ||
+ | //some very involved procedure | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | Point3(double x, double y) { | ||
+ | this.x = x; | ||
+ | this.y = y; | ||
+ | precomputedHash = countHash(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Настраиваемая сериализация | ||
+ | * Методы | ||
+ | * readObject(ObjectInputStream in) -- должен прочесть состояние из потока | ||
+ | * writeObject(ObjectOutputStream out) -- должен записать состояние в поток | ||
+ | * | ||
+ | * Процесс чтения | ||
+ | * 1. Выделение памяти | ||
+ | * 2. Вызов readObject | ||
+ | * | ||
+ | */ | ||
+ | class Point4 implements Serializable { | ||
+ | private static final long serialVersionUID = 4889340678034881968L; | ||
+ | |||
+ | double x; | ||
+ | double y; | ||
+ | |||
+ | transient int precomputedHash; | ||
+ | |||
+ | public int hashCode() { | ||
+ | return precomputedHash; | ||
+ | } | ||
+ | |||
+ | private int countHash() { | ||
+ | //some very involved procedure | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | Point4(double x, double y) { | ||
+ | this.x = x; | ||
+ | this.y = y; | ||
+ | precomputedHash = countHash(); | ||
+ | } | ||
+ | |||
+ | /* | ||
+ | * Отвечает только за запись полей ЭТОГО класса, не предка и не потомка! | ||
+ | */ | ||
+ | private void writeObject(ObjectOutputStream s) throws IOException { | ||
+ | /* | ||
+ | * Реализует стандартный механизм сериализации. | ||
+ | * Может вызываться только из writeObject, иначе NotActiveException | ||
+ | */ | ||
+ | s.defaultWriteObject(); | ||
+ | } | ||
+ | |||
+ | //отвечает только за чтение полей ЭТОГО класса, не предка и не потомка! | ||
+ | private void readObject(ObjectInputStream s) throws IOException, | ||
+ | ClassNotFoundException { | ||
+ | /* | ||
+ | * Реализует стандартный механизм десериализации. | ||
+ | * Может вызываться только из readObject, иначе NotActiveException | ||
+ | */ | ||
+ | s.defaultReadObject(); | ||
+ | //восстановление инвариантов | ||
+ | precomputedHash = countHash(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | //move to Point5.java | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | /** | ||
+ | * Сериализация с несериализуемым предком. | ||
+ | * | ||
+ | * Если хочется отнаследовать сериализуемый класс от | ||
+ | * несериализуемого предка, то могут быть проблемы... | ||
+ | * | ||
+ | * Формальное требование одно -- предок должен иметь | ||
+ | * конструктор по-умолчанию. | ||
+ | * | ||
+ | * Но только ли?! | ||
+ | */ | ||
+ | class NonSerializablePoint { | ||
+ | private double x; | ||
+ | private double y; | ||
+ | |||
+ | protected double getX() {return x;} | ||
+ | |||
+ | protected double getY() {return y;} | ||
+ | |||
+ | protected void init(double x, double y) { | ||
+ | this.x = x; | ||
+ | this.y = y; | ||
+ | } | ||
+ | |||
+ | protected NonSerializablePoint() {} | ||
+ | |||
+ | public NonSerializablePoint(double x, double y) { | ||
+ | init(x, y); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Serializable subclass of nonserializable stateful class | ||
+ | class ColoredPoint extends NonSerializablePoint implements Serializable { | ||
+ | private int color; | ||
+ | |||
+ | // Конструктор использует обычный механизм | ||
+ | public ColoredPoint(double x, double y, int color) { | ||
+ | super(x, y); | ||
+ | this.color = color; | ||
+ | } | ||
+ | |||
+ | private void readObject(ObjectInputStream s) throws IOException, | ||
+ | ClassNotFoundException { | ||
+ | s.defaultReadObject(); | ||
+ | // Manually deserialize and initialize superclass state | ||
+ | double x = s.readDouble(); | ||
+ | double y = s.readDouble(); | ||
+ | init(x, y); | ||
+ | } | ||
+ | |||
+ | private void writeObject(ObjectOutputStream s) throws IOException { | ||
+ | s.defaultWriteObject(); | ||
+ | // Manually serialize superclass state | ||
+ | s.writeDouble(getX()); | ||
+ | s.writeDouble(getY()); | ||
+ | } | ||
+ | |||
+ | private static final long serialVersionUID = 1856835860954L; | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | /** | ||
+ | * Externalizable | ||
+ | * | ||
+ | * Сериализация "в ручную" | ||
+ | * | ||
+ | * void readExternal(ObjectInput in) | ||
+ | * void writeExternal(ObjectOutput out) | ||
+ | * | ||
+ | * Должен быть конструктор по-умолчанию | ||
+ | * | ||
+ | * Чтение: вызов конструктора по-умолчанию, вызов readExternal | ||
+ | */ | ||
+ | class Point6 implements Externalizable { | ||
+ | private double x; | ||
+ | private double y; | ||
+ | |||
+ | Point6(double x, double y) { | ||
+ | this.x = x; | ||
+ | this.y = y; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void writeExternal(ObjectOutput out) throws IOException { | ||
+ | out.writeDouble(x); | ||
+ | out.writeDouble(y); | ||
+ | } | ||
+ | @Override | ||
+ | public void readExternal(ObjectInput in) throws IOException, | ||
+ | ClassNotFoundException { | ||
+ | this.x = in.readDouble(); | ||
+ | this.y = in.readDouble(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * "Подмена" сериализуемого объекта. | ||
+ | * | ||
+ | * Когда вместо самого объекта сериализуется его альтернативное представление | ||
+ | * в виде другого объекта некоторого (возможно того же самого) сериализуемого класса. | ||
+ | * | ||
+ | * Object writeReplace() -- какой объект записать вместо данного | ||
+ | * Object readResolve() -- какой объект восстановить вместо прочитанного | ||
+ | * | ||
+ | * Реализуются у разных классов!!! | ||
+ | */ | ||
+ | |||
+ | } | ||
+ | |||
import java.io.Externalizable; | import java.io.Externalizable; | ||
import java.io.FileInputStream; | import java.io.FileInputStream; |
Версия 15:08, 22 мая 2013
Что почитать
- "Effective Java", Joshua Bloch, Chapter 11
- Java Object Serialization Specification
1. Serialization.java (основная часть)
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
/**
* Лекция о сериализации в Java.
*/
@SuppressWarnings({"unused"})
public class Serialization {
/****Введение****/
/**
*
* Сериализация -- запись объекта в байтовый поток.
*
* Десереализация -- CO.
*
* Все в пакете java.io
*
* Мотивация:
* 1. dump (ex. JavaBeans persistence)
* 2. RMI
*/
class Point {
double x;
double y;
}
//move to Point1.java and SimplePointTest.java
/****Процесс сериализации****/
/**
* В сериализации участвуют ВСЕ достижимые объекты.
*
* Запись:
* 1. Запись предка (если он сериализуем)
* 2. Запись полей (если они сериализуемы)
*
* Циклические зависимости корректно обрабатываются автоматически.
*
* Чтение:
* 1. Выделение памяти под объект
* 2. Чтение предка
* 3. Чтение полей
*/
/**
* Все поля должны быть сериализуемы!
*/
@SuppressWarnings("serial")
class Polygon implements Serializable {
List<Point3> vertices = new ArrayList<>();
}
/**
* Пока все просто, но впечатление обманчиво!
*
* 1. Внутреннее представление становится частью интерфейса
* Инкапсуляция летит к чертям!
* 2. Увеличивает объемы тестирования новых версий
* 3. Дополнительный конструктор!
* Необходимо обеспечивать все инварианты
* Некоторые дыры в безопасности
* 4. Все потомки становятся сериализуемыми!
*/
/****Управление сериализацией****/
/**
* Serial version UID (stream unique identifier)
*
* Применяется для обеспечения совместимости, когда версии класса изменяются
*
* По-умолчанию автоматически создается на основе имени, интерфейсов,
* public и protected полей
*
* В случае несовпадения -- InvalidClassException
*
*/
class Point2 implements Serializable {
private static final long serialVersionUID = 4889340678034881968L;
double x;
double y;
}
/**
* Модификатор transient.
* Исключает поле из процесса сериализации.
* Примеры:
* 1. Избыточная информация (предподсчитанные значения)
* 2. Несериализуемые поля
*
*/
class Point3 implements Serializable {
private static final long serialVersionUID = 4889340678034881968L;
double x;
double y;
transient int precomputedHash;
public int hashCode() {
return precomputedHash;
}
private int countHash() {
//some very involved procedure
return 0;
}
Point3(double x, double y) {
this.x = x;
this.y = y;
precomputedHash = countHash();
}
}
/**
* Настраиваемая сериализация
* Методы
* readObject(ObjectInputStream in) -- должен прочесть состояние из потока
* writeObject(ObjectOutputStream out) -- должен записать состояние в поток
*
* Процесс чтения
* 1. Выделение памяти
* 2. Вызов readObject
*
*/
class Point4 implements Serializable {
private static final long serialVersionUID = 4889340678034881968L;
double x;
double y;
transient int precomputedHash;
public int hashCode() {
return precomputedHash;
}
private int countHash() {
//some very involved procedure
return 0;
}
Point4(double x, double y) {
this.x = x;
this.y = y;
precomputedHash = countHash();
}
/*
* Отвечает только за запись полей ЭТОГО класса, не предка и не потомка!
*/
private void writeObject(ObjectOutputStream s) throws IOException {
/*
* Реализует стандартный механизм сериализации.
* Может вызываться только из writeObject, иначе NotActiveException
*/
s.defaultWriteObject();
}
//отвечает только за чтение полей ЭТОГО класса, не предка и не потомка!
private void readObject(ObjectInputStream s) throws IOException,
ClassNotFoundException {
/*
* Реализует стандартный механизм десериализации.
* Может вызываться только из readObject, иначе NotActiveException
*/
s.defaultReadObject();
//восстановление инвариантов
precomputedHash = countHash();
}
}
//move to Point5.java
/**
* Сериализация с несериализуемым предком.
*
* Если хочется отнаследовать сериализуемый класс от
* несериализуемого предка, то могут быть проблемы...
*
* Формальное требование одно -- предок должен иметь
* конструктор по-умолчанию.
*
* Но только ли?!
*/
class NonSerializablePoint {
private double x;
private double y;
protected double getX() {return x;}
protected double getY() {return y;}
protected void init(double x, double y) {
this.x = x;
this.y = y;
}
protected NonSerializablePoint() {}
public NonSerializablePoint(double x, double y) {
init(x, y);
}
}
// Serializable subclass of nonserializable stateful class
class ColoredPoint extends NonSerializablePoint implements Serializable {
private int color;
// Конструктор использует обычный механизм
public ColoredPoint(double x, double y, int color) {
super(x, y);
this.color = color;
}
private void readObject(ObjectInputStream s) throws IOException,
ClassNotFoundException {
s.defaultReadObject();
// Manually deserialize and initialize superclass state
double x = s.readDouble();
double y = s.readDouble();
init(x, y);
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
// Manually serialize superclass state
s.writeDouble(getX());
s.writeDouble(getY());
}
private static final long serialVersionUID = 1856835860954L;
}
/**
* Externalizable
*
* Сериализация "в ручную"
*
* void readExternal(ObjectInput in)
* void writeExternal(ObjectOutput out)
*
* Должен быть конструктор по-умолчанию
*
* Чтение: вызов конструктора по-умолчанию, вызов readExternal
*/
class Point6 implements Externalizable {
private double x;
private double y;
Point6(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeDouble(x);
out.writeDouble(y);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
this.x = in.readDouble();
this.y = in.readDouble();
}
}
/**
* "Подмена" сериализуемого объекта.
*
* Когда вместо самого объекта сериализуется его альтернативное представление
* в виде другого объекта некоторого (возможно того же самого) сериализуемого класса.
*
* Object writeReplace() -- какой объект записать вместо данного
* Object readResolve() -- какой объект восстановить вместо прочитанного
*
* Реализуются у разных классов!!!
*/
}
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
/**
* Лекция о сериализации в Java.
*/
@SuppressWarnings({"unused"})
public class Serialization {
/****Введение****/
/**
*
* Сериализация -- запись объекта в байтовый поток.
*
* Десереализация -- CO.
*
* Все в пакете java.io
*
* Мотивация:
* 1. dump (ex. JavaBeans persistence)
* 2. RMI
*/
class Point {
double x;
double y;
}
//move to Point1.java and SimplePointTest.java
/****Процесс сериализации****/
/**
* В сериализации участвуют ВСЕ достижимые объекты.
*
* Запись:
* 1. Запись предка (если он сериализуем)
* 2. Запись полей (если они сериализуемы)
*
* Циклические зависимости корректно обрабатываются автоматически.
*
* Чтение:
* 1. Выделение памяти под объект
* 2. Чтение предка
* 3. Чтение полей
*/
/**
* Все поля должны быть сериализуемы!
*/
@SuppressWarnings("serial")
class Polygon implements Serializable {
List<Point3> vertices = new ArrayList<>();
}
/**
* Пока все просто, но впечатление обманчиво!
*
* 1. Внутреннее представление становится частью интерфейса
* Инкапсуляция летит к чертям!
* 2. Увеличивает объемы тестирования новых версий
* 3. Дополнительный конструктор!
* Необходимо обеспечивать все инварианты
* Некоторые дыры в безопасности
* 4. Все потомки становятся сериализуемыми!
*/
/****Управление сериализацией****/
/**
* Serial version UID (stream unique identifier)
*
* Применяется для обеспечения совместимости, когда версии класса изменяются
*
* По-умолчанию автоматически создается на основе имени, интерфейсов,
* public и protected полей
*
* В случае несовпадения -- InvalidClassException
*
*/
class Point2 implements Serializable {
private static final long serialVersionUID = 4889340678034881968L;
double x;
double y;
}
/**
* Модификатор transient.
* Исключает поле из процесса сериализации.
* Примеры:
* 1. Избыточная информация (предподсчитанные значения)
* 2. Несериализуемые поля
*
*/
class Point3 implements Serializable {
private static final long serialVersionUID = 4889340678034881968L;
double x;
double y;
transient int precomputedHash;
public int hashCode() {
return precomputedHash;
}
private int countHash() {
//some very involved procedure
return 0;
}
Point3(double x, double y) {
this.x = x;
this.y = y;
precomputedHash = countHash();
}
}
/**
* Настраиваемая сериализация
* Методы
* readObject(ObjectInputStream in) -- должен прочесть состояние из потока
* writeObject(ObjectOutputStream out) -- должен записать состояние в поток
*
* Процесс чтения
* 1. Выделение памяти
* 2. Вызов readObject
*
*/
class Point4 implements Serializable {
private static final long serialVersionUID = 4889340678034881968L;
double x;
double y;
transient int precomputedHash;
public int hashCode() {
return precomputedHash;
}
private int countHash() {
//some very involved procedure
return 0;
}
Point4(double x, double y) {
this.x = x;
this.y = y;
precomputedHash = countHash();
}
/*
* Отвечает только за запись полей ЭТОГО класса, не предка и не потомка!
*/
private void writeObject(ObjectOutputStream s) throws IOException {
/*
* Реализует стандартный механизм сериализации.
* Может вызываться только из writeObject, иначе NotActiveException
*/
s.defaultWriteObject();
}
//отвечает только за чтение полей ЭТОГО класса, не предка и не потомка!
private void readObject(ObjectInputStream s) throws IOException,
ClassNotFoundException {
/*
* Реализует стандартный механизм десериализации.
* Может вызываться только из readObject, иначе NotActiveException
*/
s.defaultReadObject();
//восстановление инвариантов
precomputedHash = countHash();
}
}
//move to Point5.java
/**
* Сериализация с несериализуемым предком.
*
* Если хочется отнаследовать сериализуемый класс от
* несериализуемого предка, то могут быть проблемы...
*
* Формальное требование одно -- предок должен иметь
* конструктор по-умолчанию.
*
* Но только ли?!
*/
class NonSerializablePoint {
private double x;
private double y;
protected double getX() {return x;}
protected double getY() {return y;}
protected void init(double x, double y) {
this.x = x;
this.y = y;
}
protected NonSerializablePoint() {}
public NonSerializablePoint(double x, double y) {
init(x, y);
}
}
// Serializable subclass of nonserializable stateful class
class ColoredPoint extends NonSerializablePoint implements Serializable {
private int color;
// Конструктор использует обычный механизм
public ColoredPoint(double x, double y, int color) {
super(x, y);
this.color = color;
}
private void readObject(ObjectInputStream s) throws IOException,
ClassNotFoundException {
s.defaultReadObject();
// Manually deserialize and initialize superclass state
double x = s.readDouble();
double y = s.readDouble();
init(x, y);
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
// Manually serialize superclass state
s.writeDouble(getX());
s.writeDouble(getY());
}
private static final long serialVersionUID = 1856835860954L;
}
/**
* Externalizable
*
* Сериализация "в ручную"
*
* void readExternal(ObjectInput in)
* void writeExternal(ObjectOutput out)
*
* Должен быть конструктор по-умолчанию
*
* Чтение: вызов конструктора по-умолчанию, вызов readExternal
*/
class Point6 implements Externalizable {
private double x;
private double y;
Point6(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeDouble(x);
out.writeDouble(y);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
this.x = in.readDouble();
this.y = in.readDouble();
}
}
/**
* "Подмена" сериализуемого объекта.
*
* Когда вместо самого объекта сериализуется его альтернативное представление
* в виде другого объекта некоторого (возможно того же самого) сериализуемого класса.
*
* Object writeReplace() -- какой объект записать вместо данного
* Object readResolve() -- какой объект восстановить вместо прочитанного
*
* Реализуются у разных классов!!!
*/
}