Java для собеседований (публикации за 24 мая 2018)5 читателей тэги

Автор: Widowmaker1984

Вопрос 92 (про клонирование объектов)

"Почему метод clone объявлен как protected? Что необходимо для реализации клонирования?"

1. Иногда необходимо получить копию объекта, которая не зависела бы от оригинала и с которой можно было бы производить манипуляции, при этом не изменяя оригинал. При обыкновенном присваивание объектов (obj1 = obj2;) передаются ссылки на объект. В итоге два экземпляра ссылаются на один объект, и изменение одного приведет к изменению другого. В данном случае, на помощь придет интерфейс Cloneable и метод clone() класса Object.

1.1. Метод clone() класса Object создает и возвращает копию объекта с такими же значениями полей. Если копируемый объект содержит ссылки на другие объекты, то ссылки будут скопированы, но дубликаты тех объектов не создаются. Это называется "поверхностным" (shallow) клонированием. Оно работает корректно (по соглашению, метод clone() должен возвращать объект независимый от клонируемого объекта), если объект содержит только примитивные поля или ссылки на неизменяемые объекты.

1.2. Обратите внимание, что метод clone нативный и делает нечто, что иными способами сделать нельзя, а именно создает копию объекта без вызова конструктора.

2. Метод clone() может выбрасывать исключение CloneNotSupportedException. Данное исключение возникает в случае, когда клонируемый класс не имеет реализации интерфейса Cloneable. Интерфейс Cloneable не реализует ни одного метода. Он является всего лишь маркером, говорящим, что данный класс реализует клонирование объекта.

2.1. Сам Object не реализует Clonable, а метод объявлен, как protected. Поэтому он доступен только при наследовании от объекта. Но это не является проблемой, потому как любой класс, является потомком класса Object. При переопределении метод clone() необходимо расширить до public.

2.2.1. Пример расширения метода clone().
public User clone() throws CloneNotSupportedException {
return (User)super.clone();
}

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

3. Перед тем как объявлять свой класс как Cloneable, следует внимательно проанализировать его структуру, и определить какие поля нужно проинициализировать после выполнения super.clone().

3.1. Из-за того, что интерфейс Cloneable не имеет публичного метода clone(), к этой операции не получается применить полиморфизм в том смысле, что для массива типа Cloneable нельзя вызвать данный метод. Поэтому по сути использование данного интерфейса ничем не лучше, чем предоставление классу аналогичной функциональности через метод с другим именем и без использования данного интерфейса.

3.2. Пример альтернативного решения — использование конструктора копирования, принимающего как параметр объект копируемого типа. Одно из преимуществ конструктора: возможность сменить тип представления данных (например, скопировать данные из LinkedList в ArrayList).

3.3. Но оба вышеуказанных способа полны потенциальных ошибок. Наиболее удобным и гибким способом клонирования является механизм сериализации. Он заключается в сохранении объекта в поток байтов с последующим восстановлением. Если нет особой необходимости обработки полей во время клонирования объектов, именно сериализация является наиболее предпочтительным вариантом для этих целей.

3.3.1. Пример:
Cat vaska = new Cat("Vaska","Gray",4);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream ous = new ObjectOutputStream(baos);
//сохраняем состояние кота Васьки в поток и закрываем его(поток)
ous.writeObject(vaska);
ous.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
//создаём кота для опытов и инициализируем его состояние Васькиным
Cat cloneVaska = (Cat)ois.readObject();

Вопросы 98, 99, 100, 107, 108, 116 (исключения и их иерархия)

"Дайте определение понятию «исключение»."
"Какова иерархия исключений?"
"Можно/нужно ли обрабатывать ошибки JVM?"
"Что вы знаете об обрабатываемых и не обрабатываемых (catched/uncatched) исключениях?"
"В чем особенность RuntimeException?"
"Что такое Error? В каком случае используется Error. Приведите пример Error’а."


1. Исключениями или исключительными ситуациями (состояниями) называются ошибки, возникшие в программе во время её работы. В случае их возникновения она может завершиться в аварийном режиме. Поэтому исключительные ситуации рекомендуется обрабатывать.

1.1. Исключения могут возникать во многих случаях, например:
- Пользователь ввел некорректные данные.
- Файл, к которому обращается программа, не найден.
- Сетевое соединение с сервером было утеряно во время передачи данных.

1.2. Обработка исключительных ситуаций (exception handling) — механизм языков программирования, предназначенный для описания реакции программы на ошибки времени выполнения и другие возможные проблемы (исключения), которые могут возникнуть и приводят к невозможности (либо бессмысленности) дальнейшей отработки программой её базового алгоритма.

скрытый текст2. Все исключения в Java являются объектами. Поэтому они могут порождаться не только автоматически при возникновении исключительной ситуации, но и создаваться самим разработчиком.

2.1. Исключения делятся на несколько классов, но все они имеют общего предка — класс Throwable. Его потомками являются подклассы Exception и Error.

2.1.1. Собственно "исключения" (Exceptions) являются результатом проблем в программе, которые в принципе решаемы и предсказуемы. Например, произошло деление на ноль в целых числах.

2.1.2. "Ошибки" (Errors) представляют собой более серьёзные проблемы, которые, согласно спецификации Java, не следует пытаться обрабатывать в собственной программе, поскольку они связаны с проблемами уровня JVM. Например, исключения такого рода возникают, если закончилась память, доступная виртуальной машине. Программа всё равно не сможет обеспечить дополнительную память для JVM.

2.1.2.1. Обрабатывать подобные ошибки можно, но делать этого не стоит., т.к. разработчику не предоставлены требуемые для этого инструменты.

2.1.2.2. Подклассы Error:
- ThreadDeath — вызывается при неожиданной остановке потока посредством метода Thread.stop().
- StackOverflowError — ошибка переполнение стека. Часто возникает в рекурсивных функциях из-за неправильного условия выхода.
- OutOfMemoryError — ошибка переполнения памяти.

2.2. В Java все исключения делятся на два типа:
- неконтролируемые исключения (unchecked), к которым относятся:
1) ошибки (Errors);
2) исключения времени выполнения (RuntimeExceptions, потомок класса Exception);
- контролируемые исключения (checked), которые можно и нужно обрабатывать в программе, к этому типу относятся все потомки класса Exception , кроме RuntimeException).

2.2.1. Основное различие в том, что обработка checked исключения проверяются на этапе компиляции. Обработка unchecked исключения происходит на этапе выполнения. Пример unchecked исключения - NullPointerException, checked исключения - IOException.

2.2.1.1. Почему не все исключения являются проверяемыми? Дело в том, что если проверять каждое место, где теоретически может быть ошибка, код сильно разрастется, станет плохо читаемым. Например в любом месте, где происходит деление чисел, нужно было бы проверять на ArithmeticException, потому что возможно деление на ноль. Эту опцию (отлавливать не проверяемые исключения) создатели языка оставили на усмотрение программиста.

2.2.2. Неконтролируемые исключения не требуют обязательной обработки, однако, при желании, можно обрабатывать исключения класса RuntimeException.

2.2.3. Класс RuntimeException extends Exception — базовый класс для ошибок во время выполнения. Относится к unchecked-исключениям. Это суперкласс, исключения которого могут быть выброшены во время нормальной работы JVM.

2.2.3.1. Подклассы RuntimeException:
- IndexOutOfBoundsException — выбрасывается, когда индекс некоторого элемента в структуре данных(массив/коллекция) не попадает в диапазон имеющихся индексов.
- NullPointerException - ссылка на объект, к которому вы обращаетесь хранит null.
- ClassCastException – Ошибка приведения типов. Всякий раз при приведении типов делается проверка на возможность приведения (проверка осуществляется с помощью instanceof.
- ArithmeticException - бросается когда выполняются недопустимые арифметические операции, например деление на ноль.

3. Список важных методов, доступных в классе Throwable:
- public String getMessage() Возврат подробного сообщения о произошедшем исключении. Инициализация данного сообщения производится в конструкторе Throwable.
- public Throwable getCause() Возврат причины исключения, представленной объектом Throwable.
- public String toString() Возврат имени класса, соединенного с результатом getMessage().
- public String printStackTrace() Выведение результата toString() совместно с трассировкой стека в System.err, поток вывода ошибок.
- public StackTraceElement [] getStackTrace() Возврат массива, содержащего каждый элемент в трассировке стека. Элемент с номером 0 представляет вершину стека вызовов, последний элемент массива отображает метод на дне стека вызовов.
- public Throwable fillInStackTrace() Заполняет трассировку стека данного объекта Throwable текущей трассировкой стека, дополняя какую-либо предшествующую информацию в трассировке стека.

Лучшее   Правила сайта   Вход   Регистрация   Восстановление пароля

Материалы сайта предназначены для лиц старше 16 лет (16+)