Java WEB knowledge resource
66. Что такое deadlock? Нарисовать схему, как это происходит.
Deadlock, он же взаимная блокировка, явление при котором все потоки находятся в режиме ожидания. Чтобы уменьшить шанс появления deadlock'a не рекомендуется использовать методы wait() и notify(). Пример: поток 1 захватывает ресурс A, поток 2 - ресурс B. Потоку 1 из ресурса А нужно захватить ресурс В, который удерживается потоком 2, а потоку 2, наоборот, нужно из ресурса В вызвать ресурс А, который удерживается потоком 1. Получается такая ситуация, что оба потока ждут, пока ресурсы станут доступны. Других потоков в системе нет и они оба бесконечно ждут. Это и называется Deadlock. Синонимом Deadlock'a называют Livelock, когда все потоки занимаются бесполезной работой и состояние системы не меняется с течением времени. ********** Взаимная блокировка (deadlock) - явление при котором все потоки находятся в режиме ожидания. Происходит, когда достигаются состояния: взаимного исключения: по крайней мере один ресурс занят в режиме неделимости и следовательно только один поток может использовать ресурс в любой данный момент времени. удержания и ожидания: поток удерживает как минимум один ресурс и запрашивает дополнительные ресурсов, которые удерживаются другими потоками. отсутствия предочистки: операционная система не переназначивает ресурсы: если они уже заняты, они должны отдаваться удерживающим потокам сразу же. цикличного ожидания: поток ждёт освобождения ресурса другим потоком, который в свою очередь ждёт освобождения ресурса заблокированного первым потоком. Простейший способ избежать взаимной блокировки - не допускать цикличного ожидания. Этого можно достичь, получая мониторы разделяемых ресурсов в определённом порядке и освобождая их в обратном порядке.
конструктор по умолчанию java
Общие сведения о конструкторах Конструктор - это схожая c методом структура, назначение которой состоит в создании экземпляра класса. Характеристики конструктора: Имя конструктора должно совпадать с именем класса (по договоренности, первая буква — заглавная, обычно имя существительное); Конструктор имеется в любом классе. Даже если вы его не написали, компилятор Java сам создаст конструктор по умолчанию (default constructor), который будет пустым и не делает ничего, кроме вызова конструктора суперкласса. Конструктор похож на метод, но не является методом, он даже не считается членом класса. Поэтому его нельзя наследовать или переопределить в подклассе; Конструкторы не наследуются; Конструкторов может быть несколько в классе. В этом случае конструкторы называют перегруженными; Если в классе не описан конструктор, компилятор автоматически добавляет в код конструктор без параметров; Конструктор не имеет возвращаемого типа, им не может быть даже тип void, если возвращается тип void, то это уже не конструктор а метод, несмотря на совпадение с именем класса. В конструкторе допускается оператор return, но только пустой, без всякого возвращаемого значения; В конструкторе допускается применение модификаторов доступа, можно задать один из модификаторов: public, protected, private или без модификатора. Конструктор не может иметь модификаторов abstract, final, native, static или synchronized; Ключевое слово this cсылается на другой конструктор в этом же классе. Если используется, то обращение должно к нему быть первой строкой конструктора; Ключевое слово super вызывает конструктор родительского класса. Если используется, должно обращение к нему быть первой строкой конструктора; Если конструктор не делает вызов конструктора super класса-предка (с аргументами или без аргументов), компилятор автоматически добавляет код вызова конструктора класса-предка без аргументов; Конструктор по умолчанию Конструктор имеется в любом классе. Даже если вы его не написали, компилятор Java сам создаст конструктор по умолчанию (default constructor). Этот конструктор пустой и не делает ничего, кроме вызова конструктора суперкласса. Т.е. если написать: public class Example {} то это эквивалентно написанию: public class Example { Example() { super; } } В данном случае явно класса предка не указано, а по умолчанию все классы Java наследуют класс Object поэтому вызывается конструктор класса Object. Если в классе определен конструктор с параметрами, а перегруженного конструктора без параметров нет, то вызов конструктора без параметров является ошибкой. Тем не менее, в Java, начиная с версии 1.5, можно использовать конструкторы с аргументами переменной длины. И если есть конструктор, имеющий аргумент переменной длины, то вызов конструктора по умолчанию ошибкой не будет. Не будет потому, что аргумент переменной длины может быть пустым. Например, следующий пример не будет компилироваться, однако если раскомментарить конструктор с аргументом переменной длины, то компиляция и запуск пройдут успешно и в результате работы строки кода DefaultDemo dd = new DefaultDemo(); вызовется конструктор DefaultDemo(int ... v). Естественно, что в данном случае необходимо пользоваться JSDK 1.5.
29. Как работает Garbage Collector. Какие самые распространенные алгоритмы? Можно ли самому указать сборщику мусора, какой объект удалить из памяти.
Функции Garbage Collector (GB) часть JVM, который призван очищать память, выделенную приложению. Он должен: найти мусор (неиспользуемые объекты) удалить мусор Есть различные реализации GB. Поиск мусора Два способа: Reference counting - у каждого объекта счетчик ссылок. Когда он равен нулю, объект считается мусором. Проблема такого подхода в том, что могут быть цикличные ссылки у объектов друг на друга, в то время как они фактически мусор и не используются программой. Tracing - объект считается не мусором, если до него можно добраться с корневых точек (GC Root: локальные переменные и параметры методов, java-потоки, статичные переменные, ссылки из JNI. Организация памяти JVM Делится на две части: Heap - куча. Основной сегмент памяти, где содержатся все объекты и происходит сборка мусора. Permanent Generation - содержит мета-данные классов. Сразу про Permanent Generation. Может менять размер во время выполнения, и это довольно дорогостоящая операция. Размер настраивается (-XX: PermSize - мин размер, -XX: MaxSize - макс размер). Часто мин = макс. Heap. Куча. Тут и работает GC. Делится на две области: New (Yang) Generation - объекты, кот. тут считаются короткоживущими. Old Generation (Tenured) - обекты считаются долгоживущими. Алгоритм GC исходит из того предположения, что большинство java-объектов живут недолго. Быстро становятся мусором. От них необходимо довольно оперативно избавляться. Что и происходит в New Generation. Там сбор мусора гораздо чаще, чем в Old Generation, где хранятся долгоживущие объекты. После создания объект попадает в New Generation и имеет шанс попасть в Old Generation по прошествии некоторого времени (циклов GC). Heap состоит из: Eden - переводится как Едем (?). Сюда аллоцируются объекты. Если нет места запускается GC. Survivor - точнее их два, S1 и S2, и они меняются ролями. Хранятся объекты, которые признаются живыми во время GC. Размер Heap настраивается. Принцип работы 4 сборщиков HotSpot VM (одна из JVM) Виды сборщиков: Serial Parallel Concurent Mark Sweep (CMS) Garbage-First (G1) Serial. Когда нет места в Eden, запускается GC, живые объекты коприруются в S1. Вся область Eden очищается. S1 и S2 меняются местами. При последующих циклах в S1 будут записаны живые объекты как из Eden, так и из S2. После нескольких циклов обмена S1 и S2 или заполнения области S2, обекты, которые живут достаточно долго перемещаются в Old Greneration. Следует сказать, что не всегда объекты при создании аллоцируюся в Eden. Если объект слишком велик, он сразу идет в Old Generation. Когда после очередной сборки мусора места нехватает уже в New Generation, то запускается сбор мусора в Old Generation (наряду со сборкой New Generation). В old Generation объекты уплотняются (алгоритм Mark-Sweep-Compact). Если после полной сборки мусора места нехватает, то вылетает Java.lang.OutOfMemoryError. Но во время работы VW может запрашивать увеличение памяти и Heap может увеличиваться. Как правило, Old Generation занимает 2/3 объема Heap. Эффективоность алгоритма сборки мусора считается по параметру STW (Stop The World) - время, когда все процессы кроме GC останавливаются. Serial в этом смысле не слишком эффективен, т.к. делает свою работу не торопясь, в одном потоке. Parallel. То же, что и Serial, но использует для работы несколько потоков. Таким образом STW чуть меньше. Concurent Mark Sweep. Принцип работы с New Generation такой же, как и в случае алгоритмов Serial и Parallel, отличия в том, что данный алгоритм разделяет младшую (New Generation) и старшую (Old Generation) сборку мусора во времени. Причем сбор мусора в Old Generation происходит в отдельном потоке, независимо от младшей сборки. При этом сначала приложение останавливается, сборщик помечает все живые объекты доступные из GC Root (корневых точек) напрямую, затем приложение вновь начинает работу, а сбощик проверяет объекты доступные по ссылкам из этих самых помеченных, и также помечает их как живые. Эта особенность создает так называемые плавающие объекты, которые помечены как живые, но таковыми по факту не являющимися. Но они будут удалены в следующих циклах. Т.е. пропускная способность растет, STW уменьшается, но требутся больше места для хранения плавающих объектов. В этом алгоритме уплотнения нет. Т.е. область Old Generation дефрагментированна. Garbage-First. G1 сильно отличается от своих предшественников. Он делит область Heap не физически, а скорее логически на те же области: Eden, Survivor, Old Generation. Причем дефрагментированно. Физически область Heap делится на регионы одинакового размера, каждый из которых может быть Eden, Survivor или Old Generation + область для больших объектов (громадный регион). Над очисткой регионов Eden работает сразу несколько потоков, объекты переносятся в регионы Survivor или регионы старшего поколения (Tenured). Это знакомый по предыдущим алгоритмам очистки подход. На время очистки работа приложения останавливается. Отличие в том, что очистка производится не по всем регионам Eden, а только по некоторым, которые более всего в ней нуждаются, таким образом регулируется время очистки. Отсюда название алгоритма - в первую очередь мусор. А с полной сборкой (точнее, здесь она называется смешанной (mixed)) все немного хитроумнее, чем в рассмотренных ранее сборщиках. В G1 существует процесс, называемый циклом пометки (marking cycle), который работает параллельно с основным приложением и составляет список живых объектов. За исключением последнего пункта, этот процесс выглядит уже знакомо для нас: Initial mark. Пометка корней (с остановкой основного приложения) с использованием информации, полученной из малых сборок. Concurrent marking. Пометка всех живых объектов в куче в нескольких потоках, параллельно с работой основного приложения. Remark. Дополнительный поиск не учтенных ранее живых объектов (с остановкой основного приложения). Cleanup. Очистка вспомогательных структур учета ссылок на объекты и поиск пустых регионов, которые уже можно использовать для размещения новых объектов. Первая часть этого шага выполняется при остановленном основном приложении. После окончания цикла пометки G1 переключается на выполнение смешанных сборок. Это значит, что при каждой сборке к набору регионов младшего поколения, подлежащих очистке, добавляется некоторое количество регионов старшего поколения. Количество таких сборок и количество очищаемых регионов старшего поколения выбирается исходя из имеющейся у сборщика статистики о предыдущих сборках таким образом, чтобы не выходить за требуемое время сборки. Как только сборщик очистил достаточно памяти, он переключается обратно в режим малых сборок. Очередной цикл пометки и, как следствие, очередные смешанные сборки будут запущены тогда, когда заполненность кучи превысит определенный порог. Опираясь на уже упомянутую статистику о предыдущих сборках, G1 может менять количество регионов, закрепленных за определенным поколением, для оптимизации будущих сборок. Громадные регионы. С точки зрения JVM объекты которые превышают размер половины региона являются громадными. Особенности: никогда не перемещается между регионами может удаляться в рамках цикла пометки или полной сборки мусора в регионе, занятом громадным объектом, может находится только он сам. Громадные объекты в силу небольшого размера регионов могут порождать проблемы с точки зрения STW. G1 выигрывает по времени STW, но расплатой является меньшая пропускная способность (около 90%, ср., например у Paraller ок. 99%) т.е. большие затраты ресурсов процессора. Bonus Вопрос: Расскажите почему именно два региона survival и зачем перекладывать объекты между ними? Ответ: Представьте себя на месте сборщика. У вас есть регион памяти, который нужно очистить. После удаления мусора регион оказывается сильно дефрагментированным и если вы хотите это исправить, то у вас есть два варианта: либо уплотнять объекты в рамках этого же региона, либо скопировать их в другой, пока еще пустой регион, располагая один-к-одному, а старый регион объявить пустым. Но задача осложняется тем, что объекты ссылаются друг на друга и при перемещении любого объекта необходимо производить обновление всех имеющихся на него ссылок. И вот эту задачу намного легче решать при копировании, причем сразу объединяя ее с задачей поиска живых объектов: Вы просто заводите два указателя на начало новой области. Первый указатель (назовем его T) смещается вправо каждый раз, когда в новую область копируется объект, то есть он всегда указывает на первый свободный блок новой области. При этом на том месте старой области, где находился перемещаемый объект, мы делаем пометку о том, что он был перемещен, и там же оставляем его новый адрес. Первым делом перемещаем таким образом все руты из старой области в новую. И вот тут вступает в действие второй указатель (назовем его R). Он тоже начинает смещаться вправо по уже размещенным в новой области объектам. В каждом объекте он ищет ссылки на другие объекты и смотрит на то место в старом регионе, куда они указывают. Если там стоит метка о перемещении и новый адрес, то этот адрес используется для подмены. Если же там лежит объект, то он перемещается в новый регион, на его месте ставится метка и новый адрес, на который так же заменяется ссылка, по которой его нашли, при этом T опять смещается вправо. Как только R догонит T, окажется, что мы собрали все живые объекты в новой области, размещенные компактно, да еще и с корректно обновленными ссылками, а старый регион можем объявить пустым. Все быстро и просто.
32. static - что такое? Что будет, если его значение изменить через объект класса? Всегда ли static поле содержит одинаковые значения для всех его объектов?
Что должен знать каждый программист о модификаторе Static в Java. В этом разделе мы рассмотрим основные моменты использования статических методов, полей и классов. Начнём с переменных. Вы НЕ можете получить доступ к НЕ статическим членам класса, внутри статического контекста, как вариант, метода или блока. Результатом компиляции приведенного ниже кода будет ошибка: public class Counter{ private int count; public static void main(String args[]){ System.out.println(count); //compile time error }} Это одна из наиболее распространённых ошибок допускаемых программистами Java, особенно новичками. Так как метод main статичный, а переменная count нет, в этом случае метод println, внутри метода main выбросит "Compile time error". В отличие от локальных переменных, статические поля и методы НЕ потокобезопасны (Thread-safe) в Java. На практике это одна из наиболее частых причин возникновения проблем связанных с безопасностью мультипоточного программирования. Учитывая что каждый экземпляр класса имеет одну и ту же копию статической переменной, то такая переменная нуждается в защите — «залочивании» классом. Поэтому при использовании статических переменных, убедитесь, что они должным образом синхронизированы (synchronized), во избежание проблем, например таких как «состояние гонки» (race condition). Статические методы имеют преимущество в применении, т.к. отсутствует необходимость каждый раз создавать новый объект для доступа к таким методам. Статический метод можно вызвать, используя тип класса, в котором эти методы описаны. Именно поэтому, подобные методы как нельзя лучше подходят в качестве методов-фабрик (factory), и методов-утилит (utility). Класс java.lang.Math — замечательный пример, в котором почти все методы статичны, по этой же причине классы-утилиты в Java финализированы (final). Другим важным моментом является то, что вы НЕ можете переопределять (Override) статические методы. Если вы объявите такой же метод в классе-наследнике (subclass), т.е. метод с таким же именем и сигнатурой, вы лишь «спрячете» метод суперкласса (superclass) вместо переопределения. Это явление известно как сокрытие методов (hiding methods). Это означает, что при обращении к статическому методу, который объявлен как в родительском, так и в дочернем классе, во время компиляции всегда будет вызван метод исходя из типа переменной. В отличие от переопределения, такие методы не будут выполнены во время работы программы. Рассмотрим пример: class Vehicle{ public static void kmToMiles(int km){ System.out.println("Внутри родительского класса/статического метода"); } } class Car extends Vehicle{ public static void kmToMiles(int km){ System.out.println("Внутри дочернего класса/статического метода "); } } public class Demo{ public static void main(String args[]){ Vehicle v = new Car(); v.kmToMiles(10); }} Вывод в консоль: Внутри родительского класса/статического метода Код наглядно демонстрирует: несмотря на то, что объект имеет тип Car, вызван статический метод из класса Vehicle, т.к. произошло обращение к методу во время компиляции. И заметьте, ошибки во время компиляции не возникло! Объявить статическим также можно и класс, за исключением классов верхнего уровня. Такие классы известны как «вложенные статические классы» (nested static class). Они бывают полезными для представления улучшенных связей. Яркий пример вложенного статического класса — HashMap.Entry, который предоставляет структуру данных внутри HashMap. Стоит заметить, также как и любой другой внутренний класс, вложенные классы находятся в отдельном файле .class. Таким образом, если вы объявили пять вложенных классов в вашем главном классе, у вас будет 6 файлов с расширением .class. Ещё одним примером использования является объявление собственного компаратора (Comparator), например компаратор по возрасту (AgeComparator) в классе сотрудники (Employee). Модификатор static также может быть объявлен в статичном блоке, более известным как «Статический блок инициализации» (Static initializer block), который будет выполнен во время загрузки класса. Если вы не объявите такой блок, то Java соберёт все статические поля в один список и выполнит его во время загрузки класса. Однако, статичный блок НЕ может пробросить перехваченные исключения, но может выбросить не перехваченные. В таком случае возникнет «Exception Initializer Error». На практике, любое исключение возникшее во время выполнения и инициализации статических полей, будет завёрнуто Java в эту ошибку. Это также самая частая причина ошибки «No Class Def Found Error», т.к. класс не находился в памяти во время обращения к нему. Полезно знать, что статические методы связываются во время компиляции, в отличие от связывания виртуальных или не статических методов, которые связываются во время исполнения на реальном объекте. Следовательно, статические методы не могут быть переопределены в Java, т.к. полиморфизм во время выполнения не распространяется на них. Это важное ограничение, которое необходимо учитывать, объявляя метод статическим. В этом есть смысл, только тогда, когда нет возможности или необходимости переопределения такого метода классами-наследниками. Методы-фабрики и методы-утилиты хорошие образцы применения модификатора static. Джошуа Блох выделил несколько преимуществ использования статичного метода-фабрики перед конструктором, в книге «Effective Java», которая является обязательной для прочтения каждым программистом данного языка. Важным свойством статического блока является инициализация. Статические поля или переменные инициализируются после загрузки класса в память. Порядок инициализации сверху вниз, в том же порядке, в каком они описаны в исходном файле Java класса. Поскольку статические поля инициализируются на потокобезопасный манер, это свойство также используется для реализации паттерна Singleton. Если вы не используется список Enum как Singleton, по тем или иным причинам, то для вас есть хорошая альтернатива. Но в таком случае необходимо учесть, что это не «ленивая» инициализация. Это означает, что статическое поле будет проинициализировано ещё ДО того как кто-нибудь об этом «попросит». Если объект ресурсоёмкий или редко используется, то инициализация его в статическом блоке сыграет не в вашу пользу. Во время сериализации, также как и transient переменные, статические поля не сериализуются. Действительно, если сохранить любые данные в статическом поле, то после десериализации новый объект будет содержать его первичное (по-умолчанию) значение, например, если статическим полем была переменная типа int, то её значение после десериализации будет равно нулю, если типа float - 0.0, если типа Object - null. Честно говоря, это один из наиболее часто задаваемых вопросов касательно сериализации на собеседованиях по Java. Не храните наиболее важные данные об объекте в статическом поле! И напоследок, поговорим о static import. Данный модификатор имеет много общего со стандартным оператором import, но в отличие от него позволяет импортировать один или все статические члены класса. При импортировании статических методов, к ним можно обращаться как будто они определены в этом же классе, аналогично при импортировании полей, мы можем получить доступ без указания имени класса. Данная возможность появилась в Java версии 1.5, и при должном использовании улучшает читабельность кода. Наиболее часто данная конструкция встречается в тестах JUnit, т.к. почти все разработчики тестов используют static import для assert методов, например assertEquals() и для их перегруженных дубликатов. Если ничего не понятно - добро пожаловать за дополнительной информацией.
39. Понятие Юникод. UTF-8, описание кодировки. Отличие от UTF-16
Я считаю, что есть много хороших статей об этом в Интернете, но вот краткое резюме. И UTF-8, и UTF-16 являются кодировками переменной длины. Однако в UTF-8 символ может занимать минимум 8 бит, в то время как в UTF-16 длина символа начинается с 16 бит. Основные плюсы UTF-8: > Основные символы ASCII, такие как цифры, латинские символы без акцентов и т. Д., Занимают один байт, который идентичен представлению US-ASCII. Таким образом все строки US-ASCII становятся действительными UTF-8, что обеспечивает приличную обратную совместимость во многих случаях. > Нет нулевых байтов, что позволяет использовать строки с нулевым символом в конце, что также обеспечивает большую обратную совместимость. > UTF-8 не зависит от порядка байтов, поэтому вам не нужно беспокоиться о проблеме Big Endian / Little Endian. Основные минусы UTF-8: > Многие обычные символы имеют разную длину, что замедляет индексацию по кодам и ужасно вычисляет количество кодов. > Хотя порядок байтов не имеет значения, иногда в UTF-8 все еще имеется спецификация (метка порядка байтов), которая служит для уведомления о том, что текст кодируется в UTF-8, а также нарушает совместимость с программным обеспечением ASCII, даже если текст содержит только ASCII. персонажи. Программное обеспечение Microsoft (например, Блокнот) особенно любит добавлять спецификации в UTF-8. Основные плюсы UTF-16: > Символы BMP (базовая многоязычная плоскость), включая латиницу, кириллицу, большинство китайских языков (КНР сделала поддержку некоторых кодовых точек вне BMP обязательной), большинство японских языков могут быть представлены 2 байтами. Это ускоряет индексирование и вычисление количества кодовых точек в случае, если текст не содержит дополнительных символов. > Даже если в тексте есть дополнительные символы, они по-прежнему представлены парами 16-битных значений, что означает, что общая длина по-прежнему делится на два и позволяет использовать 16-битный символ в качестве примитивного компонента строки. Основные минусы UTF-16: > Много нулевых байтов в строках US-ASCII, что означает отсутствие строк с нулевым символом в конце и много потерянной памяти. > Использование его в качестве кодировки фиксированной длины «в основном работает» во многих распространенных сценариях (особенно в США / ЕС / странах с кириллицей / Израилем / арабскими странами / Ираном и многими другими), что часто приводит к прерывистой поддержке, где это не работает , Это означает, что программисты должны знать о суррогатных парах и правильно обращаться с ними в тех случаях, когда это важно! > Это переменная длина, поэтому подсчет или индексирование кодовых точек обходятся дорого, хотя и меньше, чем UTF-8. В общем, UTF-16 обычно лучше для представления в памяти, тогда как UTF-8 чрезвычайно хорош для текстовых файлов и сетевых протоколов. ************************* Информации много и имеет смысл привести краткую выжимку всего, что было написано выше: Юникод постулирует чёткое разграничение между символами, их представлением в компьютере и их отображением на устройстве вывода. Юникод-символы не всегда соответствуют символу в традиционно-наивном понимании, например букве, цифре, пунктуационному знаку или иероглифу. Кодовое пространство Юникода состоит из 1 114 112 кодовых позиций в диапазоне от 0 до 10FFFF. Базовая многоязыковая плоскость включает в себя юникод-символы от U+0000 до U+FFFF, которые кодируются в UTF-16 двумя байтами. Любая юникод-кодировка позволяет закодировать всё пространство кодовых позиций Юникода и конвертация между различными такими кодировками осуществляется без потерь информации. Однобайтные кодировки позволяют закодировать лишь небольшую часть юникод-спектра, но могут оказаться полезными при работе с большим объёмом моноязыковой информации. Кодировки UTF-8 и UTF-16 обладают переменной длиной кода. В UTF-8 каждый юникод-символ может быть закодирован одним, двумя, тремя или четырьмя байтами. В UTF-16 — двумя или четырьмя байтами. Внутренний формат хранения текстовой информации в рамках отдельного приложения может быть произвольным при условии корректной работы со всем пространством кодовых позиций Юникода и отсутствии потерь при трансграничной передаче данных. Юникод и UTF-8 Довольно скоро стало понятно, что все необходимые символы невозможно вместить в таблицу, используя только один байт. Современные, более ёмкие кодировки требовали использования больших объёмов. Ранее мы упоминали, что Юникод сам по себе не является кодировкой. И вот почему. Юникод не содержит указаний по извлечению из текста бит, он работает только с кодовыми точками. В нём нет стандарта конверсии текста в двоичные данные и обратно. Юникод является абстрактным стандартом кодировки. Для практического его применения чаще всего используют схему UTF-8. Стандарт Юникод (таблица соответствий символов кодовыми точкам) определяет несколько различных кодировок на основе единого набора символов. Как и менее распространённые UTF-16 и UTF-32, UTF-8 — формат кодировки для отображения символов Юникода в двоичном виде, используя один или несколько байт на один символ. UTF-16 и UTF-32 мы обсудим чуть позже, но пока нам интересен UTF-8 как самый популярный формат. Сначала требуется разобрать термины «кодирование» и «декодирование».
44. Что такое Error. Перехват Error. Можно ли, есть ли смысл, в каких случаях возникает и что с ним делать.
Error — это критическая ошибка во время исполнения программы, связанная с работой виртуальной машины Java. В большинстве случаев Error не нужно обрабатывать, поскольку она свидетельствует о каких-то серьезных недоработках в коде. Наиболее известные ошибки: StackOverflowError — возникает, например, когда метод бесконечно вызывает сам себя, и OutOfMemoryError — возникает, когда недостаточно памяти для создания новых объектов. Как видишь, в этих ситуациях чаще всего обрабатывать особо нечего — код просто неправильно написан и его нужно переделывать.
38. (сериализация)Возможно ли сохранить объект не в байт-код, а в xml-файл?
JAXB - Java Architecture for XML Binding. JAXB входит в стандартную версию JDK версии 1.6+. Таким образом, это FREE и никаких дополнительных библиотек для загрузки и управления. Простой пример можно найти здесь XStream кажется мертвым. Последнее обновление было 6 декабря 2008 года. Simple кажется таким же простым и простым, как JAXB, но я не мог найти никакой информации о лицензировании, чтобы оценить его для использования на предприятии. In this tutorial, we're going to look at how to serialize Java objects to XML data using Jackson 2.x and deserialize it back to a POJO. My vote would be for XStream. The lack of generics support is a small price to pay for the amount of flexibility it offers. You can also easily implement generics by serializing the generic class type at serialization time, or build this into your domain objects. E.g. class Customer { List<Order> orders; public List<Order> getOrders() { return orders; } } In the stream, the element type denotes the type of each object. When the type is an abstract type or interface, the element listed with the implementing class, unless that has been specified as the default for that interface type. (E.g. ArrayList as the default for instances of static type List.) Generics are "rose coloured glasses" in java - they don't really change what you are seeing, just how you see it. The objects would be sent over the wire exactly the same if they were sent with generics support or not. ************ JAXB is pretty painless, and comes with Java 6, and it has a low footprint. **************** Стоит отметить, что начиная с версии 1.4 Java имел классы java.beans.XMLEncoder и java.beans.XMLDecoder. Эти классы выполняют XML-кодирование, которое по крайней мере очень сопоставимо с XML-сериализацией, и в некоторых случаях может сделать трюк для вас. Если ваш класс придерживается спецификации JavaBeans для своих геттеров и сеттеров, этот метод прост в использовании и вам не нужна схема. Со следующими оговорками: Как и при обычной сериализации Java кодирование и декодирование выполняются через InputStream и OutputStream процесс использует методы writeObject для familar и readObject В отличие от обычной сериализации Java кодирование, а также декодирование вызывает вызовы конструкторов и инициализаторов кодирование и декодирование независимо от того, реализует ли ваш класс Serializable или нет переходные модификаторы не учитываются. работает только для общедоступных классов, у которых есть публичные конструкторы.
Каким образом передаются переменные в методы по значению или по ссылке?
Java спецификация гласит, что все в Java передается по значению. Нет такого понятия, как «передача по ссылке» в Java. Эти условия связаны с вызовом методов и передачей переменных, как параметров метода. Хорошо, примитивные типы всегда передаются по значению без какой-либо путаницы. Но, концепция должна быть понятна в контексте параметра метода сложных типов. В Java, когда мы передает ссылку сложного типа как любой параметр метода, всегда адрес памяти копируется в новую ссылочную переменную шаг за шагом. Посмотрите на изображение: Java Core. Вопросы к собеседованию, ч. 1 - 2 В приведенном примере, биты адреса первого экземпляра копируются другой ссылочной переменной, в результате чего обе ссылки указывают на один участок памяти, где хранится объект. Помните, что присвоив второй ссылке null, вы не присвоите null первой ссылке. Но изменение состояния объекта с одной ссылающейся переменной, будет отображено и в другой ссылке. Подробности смотрите тут. Java is always pass-by-value. Unfortunately, when we pass the value of an object, we are passing the reference to it. This is confusing to beginners. It goes like this: public static void main(String[] args) { Dog aDog = new Dog("Max"); Dog oldDog = aDog; // we pass the object to foo foo(aDog); // aDog variable is still pointing to the "Max" dog when foo(...) returns aDog.getName().equals("Max"); // true aDog.getName().equals("Fifi"); // false aDog == oldDog; // true } public static void foo(Dog d) { d.getName().equals("Max"); // true // change d inside of foo() to point to a new Dog instance "Fifi" d = new Dog("Fifi"); d.getName().equals("Fifi"); // true } In the example above aDog.getName() will still return "Max". The value aDog within main is not changed in the function foo with the Dog "Fifi" as the object reference is passed by value. If it were passed by reference, then the aDog.getName() in main would return "Fifi" after the call to foo. Likewise: public static void main(String[] args) { Dog aDog = new Dog("Max"); Dog oldDog = aDog; foo(aDog); // when foo(...) returns, the name of the dog has been changed to "Fifi" aDog.getName().equals("Fifi"); // true // but it is still the same dog: aDog == oldDog; // true } public static void foo(Dog d) { d.getName().equals("Max"); // true // this changes the name of d to be "Fifi" d.setName("Fifi"); } In the above example, Fifi is the dog's name after call to foo(aDog) because the object's name was set inside of foo(...). Any operations that foo performs on d are such that, for all practical purposes, they are performed on aDog, but it is not possible to change the value of the variable aDog itself. public class Test { static String str = "Value 1"; public static void changeIt(String s) { s = "Value 2"; } public static void main(String[] args) { changeIt(str); System.out.println(str); } } Как думаете, что будет передано в метод и что напечатает код? Ответ - Value 1. Раньше я думал, что раз String не примитив, то будет происходить передача по ссылке и новое значение ("Value 2") будет успешно присвоено. В Java все передается по значению: и примитивы, и ссылки. Т.о. внутрь функции передается копия ссылки, которая ссылается на "Value 1". Присваивание данной копии значения другой ссылки (после чего она будет ссылаться на "Value 2") не влияет на объект, на который она изначально ссылалась. Объект можно изменить по ссылке (если он не immutable как String): public class Test { static StringBuilder str = new StringBuilder("Value 1"); public static void changeIt(StringBuilder s) { s.append("2"); } public static void main(String[] args) { changeIt(str); System.out.println(str); } } (Напечатает Value 12)
Класс Object
Object это базовый класс для всех остальных объектов в Java. Каждый класс наследуется от Object. Это суперкласс. Соответственно все классы наследуют методы класса Object. Методы: boolean equals (Object o); int hashCode(); вызов hashCode() объекта Object clone(); созд. новый объект - копию текущего void finalize(); вызывается перед удалением объекта Class˂?˃ getClass(); получает class объекта void notify(); возобновляет работу потока,кот. ожидает void notifyAll(); возобновляет работу всех потоков void wait();ожид.другого потока выполнения void wait(long ms); ожид.другого потока выполнения void wait(long ms,int ns) ожид.другого потока выполнения String toString () -возвр.строку описывающую объект getClass(), notify() , notifyAll(), wait() -финальные, нельзя переопределить. В Java есть специальный суперкласс Object и все классы являются его подклассами. Поэтому ссылочная переменная класса Object может ссылаться на объект любого другого класса. Так как массивы являются тоже классами, то переменная класса Object может ссылаться и на любой массив. // применимо к любому классу Object obj = new Cat("Barsik"); В таком виде объект обычно не используют. Чтобы с объектом что-то сделать, нужно выполнить приведение типов. Cat cat = (Cat) obj; У класса есть несколько важных методов. Object clone() - создаёт новый объект, не отличающий от клонируемого boolean equals(Object obj) - определяет, равен ли один объект другому void finalize() - вызывается перед удалением неиспользуемого объекта Class<?> getClass() - получает класс объекта во время выполнения int hashCode() - возвращает хеш-код, связанный с вызывающим объектом void notify() - возобновляет выполнение потока, который ожидает вызывающего объекта void notifyAll() - возобновляет выполнение всех потоков, которые ожидают вызывающего объекта String toString() - возвращает строку, описывающий объект void wait() - ожидает другого потока выполнения void wait(long millis) - ожидает другого потока выполнения void wait(long millis, int nanos) - ожидает другого потока выполнения Методы getClass(), notify(), notifyAll(), wait() являются финальными и их нельзя переопределять. Метод hashCode() Хеш-код - это целое число, генерируемое на основе конкретного объекта. Его можно рассматривать как шифр с уникальным значением. Для вычисления хеш-кода в классе String применяется следующий алгоритм. int hash = 0; for(int i = 0; i < length(); i++) hash = 31 * hash + charAt(i); У любого объекта имется хеш-код, определяемый по умолчанию, который вычисляется по адресу памяти, занимаемой объектом. Значение хеш-кода возвращает целочисленное значение, в том числ и отрицательное. Если в вашем классе переопределяется метод equals(), то следует переопределить и метод hashCode(). Метод toString() Очень важный метод, возвращающий значение объекта в виде символьной строки. Очень часто при использовании метода toString() для получения описания объекта можно получить набор бессмысленных символов, например, [I@421199e8. На самом деле в них есть смысл, доступный специалистом. Он сразу может сказать, что мы имеем дело с одномерным массивом (одна квадратная скобка), который имеет тип int (символ I). Остальные символы тоже что-то означают, но вам знать это не обязательно. Если же вам нужно научное объяснение, то метод работает по следующему алгоритму (из документации). getClass().getName() + '@' + Integer.toHexString(hashCode()) Обычно принято переопределять метод, чтобы он выводил результат в читаемом виде.
57. Как создать поток? Какими способами можно создать поток, запустить его, остановить?
Runnable Поток это объект, реализующий интерфейс Runnable Всего один метод - run() Public class HelloRunnable implements Runnable{ public void run() { System.out.println("Hello"); } public static void main(String[] args){ (new Thread(new HelloRunnable())).start(); } } Class MyThread extends Thread Class Thread реализует интерфейс Runnable Thread содержит метод start() - запуск нового потока public class MyThread extends Thread { public void run() { System.out.println("Hello"); } public static void main(String[] args){ (new MyThread ()).start(); } } + и - Runnable Можно наследовать класс отличный от Thread Runnable класс нужно передавать в конструктор Thread объекта Thread Содержит методы управления потоком Thread thread = Thread.currentThread(); Текущий Thread object можно получить в любом месте кода Некоторые методы long getId() String getName() int getPriority() void setPriority(int priority) Static void sleep(long ms) void interrupt() Если нужно остановить выполнение потока thread.sleep(1000); Если нужно прервать выполнение потока thread.interrupt() - пошлет прерывание потоку thread Если надо остановить текущий поток до окончания другого потока thread.join() почитать у callable про исключения
Что такое classpath. Если в classpath есть две одинаковые библиотеки (или разные версии одной библиотеки), объект класса из какой библиотеки создастся?
The CLASSPATH is an environment variable that tells the Java compiler javac.exe where to look for class files to import or java.exe where to find class files to interpret. In contrast, the PATH is an environment variable that tells the command processor the where to look for executable files, e.g. *.exe, *.com and *.bat files. The Classpath is one of the most confusing things in Java. Unfortunately, you must master it to even compile HelloWorld. понял я то, что при указании двух одинаковых будет загружена первая (которая первая указана), если ее не найдет, будет загружена вторая. а в случае с разными версиями? тоже будет загружен та, которая указана первой? хотят тут есть вариант, что версия библиотеки не будет указана? или этот вариант невозможен?) http://mindprod.com/jgloss/classpath.html http://java-online.ru/java-classloader.xhtml
Чем отличается finally от finalize java
final - объявление константы. finally - управляет исключениями. Блок finally необязательный и обеспечивает механизм очистки независимо от того, что произошло в try блоке. Используйте finally для закрытия файлов или для освобождения других системных ресурсов таких, как соединения с базой данных. finalize() - метод, который помогает при сборке мусора. Метод, который вызывается перед тем, как объект будет уничтожен сборщиком мусора.Не должен быть использован для освобождения ресурсов вне оперативной памяти, потому что Java имеет лимитированное количество этих ресурсов.
Какие области памяти использует java для размещения простых типов, объектов, методов и т.
https://habr.com/ru/post/84165/ JVM изнутри - организация памяти внутри процесса Java Java Наверное, все, работающие с Java, знают об управлении памяти на уровне, что для ее распределения используется сборщик мусора. Не все, к сожалению, знают, как именно этот сборщик (-и) работает, и как именно организована память внутри процесса Java. Из-за этого иногда делается неверный вывод, что memory leaks в Java не бывает, и слишком задумываться о памяти не надо. Так же часто идут холивары по поводу чрезмерного расхода памяти. Все описанное далее относится к Sun-овской реализации JVM (HotSpot), версий 5.0+, конкретные детали и алгоритмы могут различаться для разных версий. Итак, память процесса различается на heap (куча) и non-heap (стек) память, и состоит из 5 областей (memory pools, memory spaces): • Eden Space (heap) - в этой области выделятся память под все создаваемые из программы объекты. Большая часть объектов живет недолго (итераторы, временные объекты, используемые внутри методов и т.п.), и удаляются при выполнении сборок мусора это области памяти, не перемещаются в другие области памяти. Когда данная область заполняется (т.е. количество выделенной памяти в этой области превышает некоторый заданный процент), GC выполняет быструю (minor collection) сборку мусора. По сравнению с полной сборкой мусора она занимает мало времени, и затрагивает только эту область памяти — очищает от устаревших объектов Eden Space и перемещает выжившие объекты в следующую область. • Survivor Space (heap) - сюда перемещаются объекты из предыдущей, после того, как они пережили хотя бы одну сборку мусора. Время от времени долгоживущие объекты из этой области перемещаются в Tenured Space. • Tenured (Old) Generation (heap) — Здесь скапливаются долгоживущие объекты (крупные высокоуровневые объекты, синглтоны, менеджеры ресурсов и проч.). Когда заполняется эта область, выполняется полная сборка мусора (full, major collection), которая обрабатывает все созданные JVM объекты. • Permanent Generation (non-heap) - Здесь хранится метаинформация, используемая JVM (используемые классы, методы и т.п.). В частноси • Code Cache (non-heap) — эта область используется JVM, когда включена JIT-компиляция, в ней кешируется скомпилированный платформенно — зависимый код. Вот тут — blogs.sun.com/vmrobot/entry/основы_сборки_мусора_в_hotspot есть хорошее описание работы сборщиков мусора, перепечатывать не вижу смысла, советую всем интересующимся ознакомиться подробней по ссылке. https://topjava.ru/blog/stack-and-heap-in-java?utm_referrer=https%3a%2f%2fwww.google.com%2f
написать equals и для класса с одним полем String
public class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || obj.getClass() != this.getClass()) { return false; } Person guest = (Person) obj; return id == guest.id && (name == guest.name || (name != null &&name.equals(guest.getName()))); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + id; result = prime * result; return result; } } public class Main { private int x; private String y; } public boolean equals(Object o) { if( o == this) return true; if( o == null ) return false; if( getClass() != o.getClass()) return false; Main other=(Main) o; if(x!= other.x) return false; if(y==null){ if(other.y!=null) {return false;} } else if(!y.equals(other.y)){ return false;} }return true; } public int hashCode() { final prime = 31; return prime*x + ((y == null) ?: y.hashCode())
60. Чем отличается работа метода wait с параметром и без параметра?
wait() без параметров освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод notify()/notifyAll(), с параметрами заставит поток ожидать заданное количество времени или вызова notify()/notifyAll().
чем отличается абстрактный класс от интерфейса java
Абстрактный(Abstract) класс - класс, который имеет хотя бы 1 абстрактный (не определенный) метод; обозначается как abstract. Интерфейс - такой же абстрактный класс,только в нем не может быть свойств и не определены тела у методов. Так же стоит заметить, что абстрактный класс наследуется(etxends), а интерфейс реализуется (implements). Вот и возникает разница между ними, что наследовать мы можем только 1 класс, а реализовать сколько угодно. ВАЖНО! При реализации интерфейса, необходимо реализовать все его методы, иначе будет Fatal error, так же это можно избежать, присвоив слово abstract.
35. Autoboxing и unboxing. Принцип действия на примерах.
Автоупаковка Это автоматическая инкапсуляция примитивного типа в эквивалентную ему класс-обёртку всякий раз, когда требуется объект данного типа. Про инкапсуляцию и другие принципы ООП существует замечательная статья от ffriend. Autoboxing происходит: При присвоении значения примитивного типа переменной соответствующего класса-обёртки. При передаче примитивного типа в параметр метода, ожидающего соответствующий ему класс-обёртку. Примеры До JDK 5 public class Main { public static void main(String[] args) { Integer iOb = new Integer(7); Double dOb = new Double(7.0); Character cOb = new Character('a'); Boolean bOb = new Boolean(true); method(new Integer(7)); } public static void method(Integer iOb) { System.out.println("Integer"); } } Начиная с JDK 5 public class Main { public static void main(String[] args) { Integer iOb = 7; Double dOb = 7.0; Character cOb = 'a'; Boolean bOb = true; method(7); } public static void method(Integer iOb) { System.out.println("Integer"); } } Автораспаковка Это преобразование класса-обёртки в соответствующий ему примитивный тип. Если при распаковке класс-обёртка был равен null, произойдет исключение java.lang.NullPointerException. Unboxing происходит: При присвоении экземпляра класса-обёртки переменной соответствующего примитивного типа. В выражениях, в которых один или оба аргумента являются экземплярами классов-обёрток (кроме операции == и !=). При передаче объекта класса-обёртки в метод, ожидающий соответствующий примитивный тип. Давайте рассмотрит более детально. 1. При присвоении До JDK 5 int i = iOb.intValue(); double d = dOb.doubleValue(); char c = cOb.charValue(); boolean b = bOb.booleanValue(); Начиная с JDK 5 int i = iOb; double d = dOb; char c = cOb; boolean b = bOb; 2. В выражениях Так как арифметические операторы и операторы сравнения (исключение == и !=) применяются только к примитивным типам, приходилось делать распаковку вручную, что заметно снижало читабельность выражений, делая их громоздкими, и кода в целом. Integer iOb1 = new Integer(5); Integer iOb2 = new Integer(7); System.out.println(iOb1.intValue() > iOb2.intValue()); Благодаря автораспаковке, мы смело можем писать выражения, не используя методы конвертации. Теперь за этим следит компилятор Java. System.out.println(iOb1 > iOb2); System.out.println(iOb1 + iOb2); При сравнении классов-обёрток оператором == или !=, происходит сравнение по ссылкам, а не по значениям и может возникнуть путаница. Например, как вы думаете, что выведется на экран при исполнении следующего кода? Integer iOb1 = 100; Integer iOb2 = 100; System.out.println(iOb1 == iOb2); Integer iOb3 = new Integer(120); Integer iOb4 = new Integer(120); System.out.println(iOb3 == iOb4); Integer iOb5 = 200; Integer iOb6 = 200; System.out.println(iOb5 == iOb6); Ответ: в первом случае — true, во втором и третьем — false. В первом случае фактически вызывается статичный метод java.lang.Integer.valueOf(int), который кэширует значения от -128 до 127 (верхнюю границу можно изменять) и при повторном использовании достает их из так называемого pool (набор инициализированных и готовых к использованию объектов). Во втором происходит явное создание объектов, следовательно они имеют разные ссылки. 3. При передачи в метод public class Main { public static void main(String[] args) { Integer iOb = 10; method(iOb); } public static void method(int i) { System.out.println("int"); } } На экран будет выведено int, но стоит отметить, что если для метода реализована перегрузка с соответствующим классом-обёрткой, вызовется именно он. public class Main { public static void main(String[] args) { Integer iOb = 10; method(iOb); } public static void method(int i) { System.out.println("int"); } public static void method(Integer iOb) { //Будет вызван данный метод System.out.println("Integer"); } } Так же следует помнить, что автоупаковка и автораспаковка не работает для массивов. public class Main { public static void main(String[] args) { Integer[] iObs = new Integer[] {5, 10, 50, 2, 7}; method(iObs); //Ошибка компиляции } public static void method(int ... i) { System.out.println("int[]"); } } Плохая производительность Классы-обёртки неизменяемые, поэтому при каждой автоупаковке (за исключением значений из pool) создается новый объект, что может привести к неразумному расходу памяти. public static Integer sumBeforeInclusive(Integer number) { Integer iOb = number; if (number > 1) iOb += sumBeforeInclusive(number - 1); return iOb; } ************!!!!!!!!!!!!!!******************* Автоупаковка и распаковка выполняется при перегрузке метода на основании следующих правил: Расширение "побеждает" упаковку в ситуации, когда становится выбор между расширением и упаковкой, расширение предпочтительней. public class WideBoxed { public class WideBoxed { static void methodWide(int i) { System.out.println("int"); } static void methodWide( Integer i ) { System.out.println("Integer"); } public static void main(String[] args) { short shVal = 25; methodWide(shVal); } } } Вывод программы — тип int ************!!!!!!!!!!!!!!!!!****************** Расширение побеждает переменное количество аргументов в ситуации, когда становится выбор между расширением и переменным количеством аргументов, расширение предпочтительней. Листинг 5: Пример кода, показывающий преимущество перегрузки public class WideVarArgs { static void methodWideVar(int i1, int i2) { System.out.println("int int"); } static void methodWideVar(Integer... i) { System.out.println("Integers"); } public static void main( String[] args) { short shVal1 = 25; short shVal2 = 35; methodWideVar( shVal1, shVal2); } } Вывод программы — тип int int ******************* Упаковка побеждает переменное количество аргументов в ситуации, когда становится выбор между упаковкой и переменным количеством аргументов, упаковка предпочтительней. Листинг 6: Пример кода, показывающий преимущество перегрузки public class BoxVarargs { static void methodBoxVar(Integer in) { System.out.println("Integer"); } static void methodBoxVar(Integer... i) { System.out.println("Integers"); } public static void main(String[] args) { int intVal1 = 25; methodBoxVar(intVal1); } } Вывод программы — тип Integer ***************** Вы должны помнить о следующих вещах, используя Автоупаковку: Как мы знаем, любая хорошая функция имеет недостаток. Автоупаковка не является исключением в этом отношении. Некоторый важные замечания, которые должен учитывать разработчик при использовании этой функции: Сравнивая объекты оператором '==' может возникнуть путаница, так как он может применяться к примитивным типам и объектам. Когда этот оператор применяется к объектам,он фактически сравнивает ссылки на объекты а не сами объекты.
34. Анонимные классы. Практическое применение
Анонимные классы декларируются внутри методов основного класса и могут быть использованы только внутри этих методов. В отличие от локальных классов, анонимные классы не имеют названия. Главное требование к анонимному классу - он должен наследовать существующий класс или реализовывать существующий интерфейс. Анонимные классы не могут содержать определение статических полей, методов и классов (кроме констант), но могут их наследовать. Пример : class OuterClass { public OuterClass() {} void methodWithLocalClass (final int interval) { // При определении анонимного класса применен полиморфизм - переменная listener содержит экземпляр // анонимного класса, реализующего существующий интерфейс ActionListener ActionListener listener = new ActionListener() { @Override public void actionPerformed(ActionEvent event) { System.out.println("Эта строка выводится на экран каждые " + interval + " секунд"); } }; Timer t = new Timer(interval, listener); // Объект анонимного класса использован внутри метода t.start(); } } Анонимные классы широко используются Java-программистами. Анонимный класс не имеют имени. Классический пример анонимного класса: new Thread(new Runnable() { public void run() { ... } }).start(); На основании анонимного класса создается поток и запускается с помощью метода start класса Thread. Синтаксис создания анонимного класса базируется на использовании оператора new с именем класса (интерфейса) и телом новосозданного анонимного класса. Основное ограничение при использовании анонимных классов - это невозможность описания конструктора, так как класс не и меет имени. Аргументы, указанные в скобках, автоматически используются для вызова конструктора базового класса с теми же параметрами. Вот пример : class Clazz { Clazz(int param) { } public static void main(String[] args) { new Clazz(1) { }; // правильное создание анонимного класса new Clazz( ) { }; // неправильное создание анонимного класса } } Так как анонимный класс является локальным классом, он имеет все те же ограничения, что и локальный класс. Использование анонимных классов оправдано во многих случаях, в частности когда: тело класса является очень коротким; нужен только один экземпляр класса; класс используется в месте его создания или сразу после него; имя класса не важно и не облегчает понимание кода. анонимные классы допускают вложенность друг в друга.
43. Опишите работу блока try-catch-finally. Может ли работать данный блок без catch
В Java есть пять ключевых слов для работы с исключениями: try — данное ключевое слово используется для отметки начала блока кода, который потенциально может привести к ошибке. catch — ключевое слово для отметки начала блока кода, предназначенного для перехвата и обработки исключений. finally — ключевое слово для отметки начала блока кода, которой является дополнительным. Этот блок помещается после последнего блока 'catch'. Управление обычно передаётся в блок 'finally' в любом случае. throw — служит для генерации исключений. throws — ключевое слово, которое прописывается в сигнатуре метода, и обозначающее что метод потенциально может выбросить исключение с указанным типом. Общий вид конструкции для «поимки» исключительной ситуации выглядит следующим образом: try{ //здесь код, который потенциально может привести к ошибке } catch(SomeException e ){ //в скобках указывается класс конкретной ожидаемой ошибки //здесь описываются действия, направленные на обработку исключений } finally{ //выполняется в любом случае ( блок finally не обязателен) } try{ //здесь код, который потенциально может привести к ошибке } catch(SomeException e ){ //в скобках указывается класс конкретной ожидаемой ошибки //здесь описываются действия, направленные на обработку исключений } finally{ //выполняется в любом случае ( блок finally не обязателен) } Подробнее http://www.quizful.net/post/java-exceptions 5. О чем говорит ключевое слово throws? throws — ключевое слово, которое прописывается в сигнатуре метода, и обозначающее что метод потенциально может выбросить исключение с указанным типом. 6. В чем особенность блока finally? Всегда ли он исполняется? Когда исключение передано, выполнение метода направляется по нелинейному пути. Это может стать источником проблем. Например, при входе метод открывает файл и закрывает при выходе. Чтобы закрытие файла не было пропущено из-за обработки исключения, был предложен механизм finally. Ключевое слово finally создаёт блок кода, который будет выполнен после завершения блока try/catch, но перед кодом, следующим за ним. Блок будет выполнен, независимо от того, передано исключение или нет. Оператор finally не обязателен, однако каждый оператор try требует наличия либо catch, либо finally. Код в блоке finally будет выполнен всегда. 7. Может ли не быть ни одного блока catch при отлавливании исключений? Такая запись допустима, если имеется связка try{} finally {}. Но смысла в такой записи не так много, всё же лучше иметь блок catch в котором будет обрабатываться необходимое исключение. String x = "z"; try { x="234"; } finally { x = "Finally"; } String x = "z"; try { x="234"; } finally { x = "Finally"; } 8. Могли бы вы придумать ситуацию, когда блок finally не будет выполнен? Блок finally выполняется не всегда, например в такой ситуации: try { System.exit(0); } catch(Exception e) { e.printStackTrace(); } finally { } try { System.exit(0); } catch(Exception e) { e.printStackTrace(); } finally { } Здесь finally недостижим, так как происходит системный выход из программы. Общими словами: когда jvm умирает, ей не до finally (отсюда можете придумать другие примеры как убить jvm и ответить на вопрос в заголовке). 9. Может ли один блок catch отлавливать несколько исключений (с одной и разных веток наследований)? В Java 7 стала доступна новая конструкция, с помощью которой можно перехватывать несколько исключений одним блоком catch: try { ... } catch( IOException | SQLException ex ) { logger.log(ex); throw ex; } try { ... } catch( IOException | SQLException ex ) { logger.log(ex); throw ex; }
модификаторы доступа java
В Java существуют следующие модификаторы доступа: private: члены класса доступны только внутри класса; default (package-private) (модификатор, по-умолчанию): члены класса видны внутри пакета (если класс будет так объявлен он будет доступен только внутри пакета); protected: члены класса доступны внутри пакета и в наследниках; public: члены класс доступны всем; Последовательность модификаторов по убыванию уровня закрытости: private, default ,protected, public). Во время наследования возможно изменения модификаторов доступа в сторону большей видимости. Так сделано для того, чтобы не нарушался принцип LSP для наследуемого класса. В языке Java есть 4 вида модификаторов доступа: default Доступен внутри пакета. private Доступен внутри класса protected Доступен внутри класса и для всех подклассов. public Доступен всем Рассмотрим каждый их них отдельно. default Этот модификатор присваивается переменной, методу или классу в том случае, если явно его не указываем. В этом случае пременная, метод или класс доступны всем классам внутри пакета. private Если мы используем модификатор доступа private для переменной, метода или конструктора, то они будут видны только внутри этого класса. Классы и интерфейсы не могут иметь модификато доступа private. Доступ к переменным возможен только через getters и setters. Пример: public class Company { private String companyName; public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } } В этом примере переменная companyName (имя компании) объявлена, как private, поэтому для доступа к ней созданы 2 метода setCompanyName() и getCompanyName. protected Переменные, методы и конструкторы с модификатором доступа protected доступны в подклассах в других пакетах и в любых классах в том же пакете. Пример: package net.proselyte.javacore.modifiers; public class Developer { protected String writeCode(){ return "Developer is writing code..."; } } И другой класс package net.proselyte.javacore.modifiers; public class JavaDeveloper extends Developer{ @Override protected String writeCode() { return "Java Developer is writing Java code..."; } } Второй класс-наследник может перезаписать метод. Елси бы мы объявили метод writeCode() в родительском классе, как private, то мы не смогли бы это сделать. public Переменные, методы, класс и т.д. с модификатором доступа public доступны из любого класса. Единственный нюанс заключается в том, что если мы пытаемся получить доступ к публичному классу из другого пакета, то мы должны импортировать класс с помощью ключевого слова import Пример: public void setId(int id){ this.id = id; } В этом уроке мы рассмотрели модификаторы доступа в языке Java.
можно ли менять возвращаемый тип метода java
Возможно ли при переопределении (override) метода изменить: Модификатор доступа Возвращаемый тип Тип аргумента или количество Имя аргументов Изменять порядок, количество или вовсе убрать секцию throws? Да, если расширять (package -> protected -> public) Да, если выполняется Downcasting(понижающее преобразование, преобразование вниз по иерархии) то есть возвращаемый тип в переопределенном методе класса наследника должен быть НЕ шире чем в классе родителе (Object -> Number -> Integer) Нет, в таком случае происходит Overload(перегрузка) Да Возможно изменять порядок. Возможно вовсе убрать секцию throws в методе, так как она уже определена. Так же возможно добавлять новые исключения, которые наследуются от объявленных или исключения времени выполнения. Переопределение методов действует при наследовании классов, т.е. в классе наследнике объявлен метод с такой же сигнатурой что и в классе родителе. Значит этот метод переопределил метод своего суперкласса. Несколько нюансов по этому поводу: Модификатор доступа в методе класса наследника должен быть НЕ уже чем в классе родителе, иначе будет ошибка компиляции. Описание исключения в переопределенном методе класса наследника должен быть НЕ шире чем в классе родителе, иначе ошибка компиляции. Метод обьявленный как "private" в классе родителе нельзя переопределить!
42. Что такое checked и unchecked Exception? Их отличия.
Все исключительные ситуации делятся на «проверяемые» (checked) и «непроверяемые» (unchecked) (смотрите картинку в начале статьи). Это свойство присуще «корневищу» (Throwable, Error, Exception, RuntimeException) и передается по наследству. Никак не видимо в исходном коде класса исключения. В дальнейших примерах просто учтите, что— Throwable и Exception и все их наследники (за исключением наследников Error-а и RuntimeException-а) — checked — Error и RuntimeException и все их наследники — unchecked checked exception = проверяемое исключение, проверяемое компилятором. Тема достаточно обширная для того, чтобы уместить ее в одном ответе. К примеру, можно разобрать примеры Головача: http://habrahabr.ru/company/golovachcourses/blog/225585/ И еще с quizful.net 1. Checked исключения, это те, которые должны обрабатываться блоком catch или описываться в сигнатуре метода. Unchecked могут не обрабатываться и не быть описанными. 2. Unchecked исключения в Java — наследованные от RuntimeException, checked — от Exception (не включая unchecked). Checked исключения отличаются от Unchecked исключения в Java, тем что: 1)Наличие\обработка Checked исключения проверяются на этапе компиляции. Наличие\обработка Unchecked исключения происходит на этапе выполнения. — проверка на cheched исключения происходит в момент компиляции (compile-time checking) — перехват исключений (catch) происходит в момент выполнения (runtime checking) *************** Throwable находится на вершине всех исключений. Под Throwable у вас есть ошибка и исключение. В разделе "Исключение" у вас есть RuntimeException. Java имеет два типа исключений - проверено и не отмечено. Проверенные исключения выполняются компилятором (вы должны объявить их в предложении throws и в конечном итоге поймать их). Исключенные исключения не применяются для ловушки или объявления в предложении throws. (Спорная часть ответа) Throwable существует, так что существует родительский для всех типов исключений. Вы никогда не должны заявлять, что бросаете Throwable и никогда не поймаете его (если вы действительно действительно не знаете, что делаете). Существует ошибка, указывающая на проблемы среды выполнения, из-за которых ваша программа, вероятно, не может восстановиться, например, файл с отформатированным файлом класса или нехватка памяти VM. Вы не должны ловить ошибку, если не знаете, что делаете. Исключение существует как корень для всех ошибок, отличных от программиста (см. RuntimeException для "исключения" ), например, файл не может быть создан, потому что диск заполнен. Вы не должны бросать, бросать или захватывать Исключение. Если вам нужно поймать Exception, убедитесь, что вы знаете, что делаете. RuntimeException существует, чтобы указать всю ошибку программиста, например, пройти мимо конца массива или вызвать метод для нулевого объекта. Это то, что вы должны исправить, чтобы они не бросали исключения - указывают, что вы, программист, напортачили код. Опять же, вы не должны их поймать, если не знаете, что делаете. +32 TofuBeer 02 июл. '10 в 2:22 источникподелиться Поскольку я являюсь новым Java-разработчиком, я также столкнулся с некоторыми трудностями при различении различных типов исключений и работе с ними. Вот почему я сделал короткую заметку на эту тему, и всякий раз, когда я запутался, я проходил через это. Вот это с изображением Throwable классов Throwable: Throwable Class Hierarchy [Изображение предоставлено JavaTpoint ]. Здесь нужно запомнить три ключевых класса: Throwable, Exception и Error. Среди этих классов Exception можно разделить на два типа: "Checked Exception" и "Unchecked Exception". Проверено исключение: Это классы, которые расширяют Throwable кроме RuntimeException и Error. Они также известны как исключения времени компиляции, потому что они проверяются во время компиляции, что означает, что компилятор заставляет нас обрабатывать их с помощью try/catch или указывает в сигнатуре функции, что он их throws и заставляет нас иметь дело с ними в вызывающей программе. Это программно восстанавливаемые проблемы, вызванные непредвиденными условиями, не зависящими от кода (например, сбой базы данных, ошибка ввода-вывода файла, неправильный ввод и т.д.). Пример: IOException, SQLException и т.д. Непроверенное исключение: Классы, которые расширяют RuntimeException, называются непроверенными исключениями. Непроверенные исключения проверяются не во время компиляции, а во время выполнения, отсюда и название. Они также являются программно восстанавливаемыми проблемами, но в отличие от проверенного исключения они вызваны ошибками в потоке кода или конфигурации. Пример: ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException и т.д. Так как они являются ошибками в программировании, их можно избежать, используя хорошее/мудрое кодирование. Например, "деление на ноль" создает исключение ArithmeticException, которого можно избежать, просто проверив делитель. Точно так же мы можем избежать исключения NullPointerException, просто проверяя ссылки: if (object != null) или даже используя лучшие методы. Ошибка: Error относится к безвозвратной ситуации, которая не обрабатывается методом try/catch. Пример: OutOfMemoryError, VirtualMachineError, AssertionError и т.д. Почему эти много типов? В дополнение к ответу Стивена С. Я хочу сказать: обработка исключений является относительно дорогой операцией в Java. Мы не должны помещать все исключительные ситуации в блок try/catch. Чрезмерное использование try/catch может снизить производительность программы. В заключение, Exception следует обрабатывать программно, когда это возможно. С другой стороны, мы не можем обработать Error s, так что это может быть несколько логических причин, по которым существует много типов исключений.
OOP abstraction. Принципы ООП.
Главное Инкапсулируйте все, что может изменяться; Уделяйте больше внимания интерфейсам, а не их реализациям; Каждый класс в вашем приложении должен иметь только одно назначение; Классы — это их поведение и функциональность. Базовые принципы ООП Абстракция — отделение концепции от ее экземпляра; Инкапсуляция — размещение одного объекта или класса внутри другого для разграничения доступа к ним. Наследование — способность объекта или класса базироваться на другом объекте или классе. Это главный механизм для повторного использования кода. Наследственное отношение классов четко определяет их иерархию; Полиморфизм — реализация задач одной и той же идеи разными способами; Используйте следующее вместе с наследованием Делегация — перепоручение задачи от внешнего объекта внутреннему; Композиция — включение объектом-контейнером объекта-содержимого и управление его поведением; последний не может существовать вне первого; Агрегация — включение объектом-контейнером ссылки на объект-содержимое; при уничтожении первого последний продолжает существование. https://tproger.ru/translations/oop-principles-cheatsheet/ ****************************** Объе́ктно-ориенти́рованное программи́рование (ООП) — это методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования. Основные принципы ООП: абстракция, инкапсуляция, наследование, полиморфизм. Абстракция — означает выделение значимой информации и исключение из рассмотрения незначимой. С точки зрения программирования это правильное разделение программы на объекты. Абстракция позволяет отобрать главные характеристики и опустить второстепенные. Пример: описание должностей в компании. Здесь название должности значимая информация, а описание обязанностей у каждой должности это второстепенная информация. К примеру главной характеристикой для «директор» будет то, что это должность чем-то управляет, а чем именно (директор по персоналу, финансовый директор, исполнительный директор) это уже второстепенная информация. Инкапсуляция — свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе. Для Java корректно будет говорить, что инкапсуляция это «сокрытие реализации». Пример из жизни — пульт от телевизора. Мы нажимаем кнопочку «увеличить громкость» и она увеличивается, но в этот момент происходят десятки процессов, которые скрыты от нас. Для Java: можно создать класс с 10 методами, например вычисляющие площадь сложной фигуры, но сделать из них 9 private. 10й метод будет называться «вычислитьПлощадь()» и объявлен public, а в нем уже будут вызываться необходимые скрытые от пользователя методы. Именно его и будет вызывать пользователь. Хороша тем,что ты показываешь только то что надо. Для того чтобы внести изменения Наследование — свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью. Класс, от которого производится наследование, называется базовым, родительским или суперклассом. Новый класс — потомком, наследником, дочерним или производным классом. Наследование реализации - одно наследование(класс) Наследование интерфейса - множественное наследование Полиморфизм — свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта. Пример (чуть переделанный) из Thinking in Java: public interface Shape { void draw(); void erase(); } public class Circle implements Shape { public void draw() { System.out.println("Circle.draw()"); } } public class Triangle implements Shape { public void draw() { System.out.println("Triangle.draw()"); } } public class TestPol { public static void main(String[] args) { Shape shape1 = new Circle(); Shape shape2 = new Triangle(); testPoly(shape1); testPoly(shape2); } public static void testPoly(Shape shape) { shape.draw(); } } //Вывод в консоль: //Circle.draw() //Triangle.draw() Есть общий интерфейс «Фигура» и две его реализации «Треугольник» и «Круг». У каждого есть метод «нарисовать». Благодаря полиморфизму нам нет нужды писать отдельный метод для каждой из множества фигур, чтобы вызвать метод «нарисовать». Вызов полиморфного метода позволяет одному типу выразить свое отличие от другого, сходного типа, хотя они и происходят от одного базового типа. Это отличие выражается различным действием методов, вызываемых через базовый класс (или интерфейс). Здесь приведен пример полиморфизма (также называемый динамическим связыванием, или поздним связыванием, или связыванием во время выполнения), в котором продемонстрировано как во время выполнения программы будет выполнен тот метод, который принадлежит передаваемому объекту. Если бы не было полиморфизма и позднего связывания, то эта же программа выглядела бы примерно так: public static void testPolyCircle(Circle circle) { circle.draw(); } public static void testPolyTriangle(Triangle triangle) { triangle.draw(); } Т.е. для каждого класса (фигуры) мы бы писали отдельный метод. Здесь их два, а если фигур (классов) сотни?
правила переопределения метода clone().
Глубокое копирование и поверхностное копирование Точной копией оригинала является его клон. В Java это означает возможность создавать объект с аналогичной структурой, как и у исходного объекта. Метод clone() обеспечивает эту функциональность. Поверхностное копирование копирует настолько малую часть информации, насколько это возможно. По умолчанию, клонирование в Java является поверхностным, т.е. Object class не знает о структуре класса, которого он копирует. При клонировании, JVM делает такие вещи: Если класс имеет только члены примитивных типов, то будет создана совершенно новая копия объекта и возвращена ссылка на этот объект. Если класс содержит не только члены примитивных типов, а и любого другого типа класса, тогда копируются ссылки на объекты этих классов. Следовательно, оба объекта будут иметь одинаковые ссылки. Глубокое копирование дублирует все. Глубокое копирование — это две коллекции, в одну из которых дублируются все элементы оригинальной коллекции. Мы хотим сделать копию, при которой внесение изменений в любой элемент копии не затронет оригинальную коллекцию. Глубокое клонирование требует выполнения следующих правил: Нет необходимости копировать отдельно примитивные данные; Все классы-члены в оригинальном классе должны поддерживать клонирование. Для каждого члена класса должен вызываться super.clone() при переопределении метода clone(); Если какой-либо член класса не поддерживает клонирование, то в методе клонирования необходимо создать новый экземпляр этого класса и скопировать каждый его член со всеми атрибутами в новый объект класса, по одному. Что нужно для реализации клонирования? Как минимум удовлетворить контракту этого метода: 1) x.clone() != x для любых x != null 2) возвращаемый объект: a) должен быть получен с помощью вызова super.clone() b) не должен зависить от объекта this class A implements Cloneable{ private Item it; public Object clone(){ A clone = (A)super.clone(); Item itClone = clone.getIt(); if (itClone instanceof Cloneable){ clone.setIt(((Item)itClone).getIt().clone()); }else{ itClone = new Item(itClone); clone.setIt(itClone); } return clone; }
36. Generics. Что это такое и для чего применяются. Во что превращается во время выполнения? Использование wildcards
Дженерики (обобщения) — это особые средства языка Java для реализации обобщённого программирования: особого подхода к описанию данных и алгоритмов, позволяющего работать с различными типами данных без изменения их описания. На сайте Oracle дженерикам посвящён отдельный tutorial: "Lesson: Generics". Во-первых, чтобы понять дженерики, нужно разобраться, зачем они вообще нужны и что они дают. В tutorial в разделе "Why Use Generics?" сказано, что одно из назначений — более сильная проверка типов во время компиляции и устранение необходимости явного приведения. После компиляции какая-либо информация о дженериках стирается. Это называется "Стирание типов" или "Type Erasure". Все будет Object. Стирание типов и дженерики сделаны так, чтобы обеспечить обратную совместимость со старыми версиями JDK, но при этом дать возможность помогать компилятору с определением типа в новых версиях Java. *********** Стирание стилей относится к использованию дженериков. Там определенно метаданные в файле класса, чтобы сказать, является ли метод/тип обобщенным, и каковы ограничения и т.д. Но когда используются дженерики, они преобразуются в проверки времени компиляции и касты выполнения. Итак, этот код: List<String> list = new ArrayList<String>(); list.add("Hi"); String x = list.get(0); скомпилирован в List list = new ArrayList(); list.add("Hi"); String x = (String) list.get(0); Во время выполнения нет способа узнать, что T=String для объекта списка - эта информация отсутствует. ... но сам интерфейс List<T> по-прежнему объявляет себя универсальным. EDIT: просто для уточнения компилятор сохраняет информацию о переменной List<String>, но вы все еще не можете узнать, что T=String для самого объекта списка. **************************** Компилятор отвечает за понимание Generics во время компиляции. Компилятор также отвечает за исключение этого "понимания" общих классов в процессе, который мы называем стиранием типа. Все происходит во время компиляции. Примечание.. Вопреки убеждениям большинства разработчиков Java, можно хранить информацию типа времени компиляции и извлекать эту информацию во время выполнения, несмотря на очень ограниченный путь. Другими словами: Java действительно предоставляет обобщенные генерические файлы очень ограниченным образом. Что касается стирания стилей Обратите внимание, что во время компиляции имеется полная информация о типе, но эта информация намеренно удаляется вообще при генерации байтового кода в процессе, известном как стирание типа. Это делается из-за проблем с совместимостью: целью языковых дизайнеров была полная совместимость с исходным кодом и полная совместимость с байтовым кодом между версиями платформы. Если бы он был реализован по-разному, вам пришлось бы перекомпилировать ваши устаревшие приложения при переходе на более новые версии платформы. Как это было сделано, все сигнатуры методов сохраняются (совместимость с исходным кодом), и вам не нужно перекомпилировать что-либо (бинарная совместимость). Что касается обобщенных дженериков в Java Если вам нужно сохранить информацию типа времени компиляции, вам необходимо использовать анонимные классы. Дело в том, что в особом случае анонимных классов можно получить полную информацию типа времени компиляции во время выполнения, что, другими словами, означает: reified generics. Это означает, что компилятор не выбрасывает информацию о типе, когда участвуют анонимные классы; эта информация хранится в сгенерированном двоичном коде, а система времени выполнения позволяет получить эту информацию. Я написал статью по этому вопросу: http://rgomes-info.blogspot.co.uk/2013/12/using-typetokens-to-retrieve-generic.html Замечание о технике, описанной в статье выше, заключается в том, что этот метод неясен для большинства разработчиков. Несмотря на то, что он работает и работает хорошо, большинство разработчиков чувствуют себя запутанными или неудобными в технике. Если у вас есть общая база кода или планируете опубликовать свой код для общественности, я не рекомендую эту технику выше. С другой стороны, если вы являетесь единственным пользователем вашего кода, вы можете воспользоваться преимуществами, которые этот метод предоставляет вам. Пример кода В приведенной выше статье есть ссылки на пример кода.
зачем переопределять equals и hashcode одновременно
Джошуа Блох говорит об эффективной Java Вы должны переопределить hashCode() в каждом классе, который переопределяет equals(). Несоблюдение этого требования приведет к нарушению общего договора для Object.hashCode(), что предотвратит правильное функционирование вашего класса в сочетании со всеми коллекциями на основе хешей, включая HashMap, HashSet и Hashtable. Контракт hashCode Для реализации хэш-функции в спецификации языка определены следующие правила: вызов метода hashCode один и более раз над одним и тем же объектом должен возвращать одно и то же хэш-значение, при условии что поля объекта, участвующие в вычислении значения, не изменялись. вызов метода hashCode над двумя объектами должен всегда возвращать одно и то же число, если эти объекты равны (вызов метода equals для этих объектов возвращает true). вызов метода hashCode над двумя неравными между собой объектами должен возвращать разные хэш-значения. Хотя это требование и не является обязательным, следует учитывать, что его выполнение положительно повлияет на производительность работы хэш-таблиц. Методы equals и hashCode необходимо переопределять вместе Исходя из описанных выше контрактов следует, что переопределяя в своем коде метод equals, необходимо всегда переопределять и метод hashCode. Так как фактически два экземпляра класса отличаются, потому что находятся в разных областях памяти, сравнивать их приходится по некоторым логическим признакам. Соответственно, два логически эквивалентных объекта, должны возвращать одинаковое значение хэш-функции. Что произойдет, если будет переопределен только один из этих методов? equals есть, hashCode нет Допустим мы правильно определили метод equals в нашем классе, а метод hashCode решили оставить как он есть в классе Object. Тогда с точки зрения метода equals два объекта будут логически равны, в то время как с точки зрения метода hashCode они не будут иметь ничего общего. И, таким образом, помещая некий объект в хэш-таблицу, мы рискуем не получить его обратно по ключу. Например, так: Map<Point, String> m = new HashMap<>(); m.put(new Point(1, 1), "Point A"); // pointName == null String pointName = m.get(new Point(1, 1)); Очевидно, что помещаемый и искомый объект — это два разных объекта, хотя они и являются логически равными. Но, т.к. они имеют разное хэш-значение, потому что мы нарушили контракт, можно сказать, что мы потеряли свой объект где-то в недрах хэш-таблицы. hashCode есть, equals нет. Что будет если мы переопределим метод hashCode, а реализацию метода equals унаследуем из класса Object. Как известно метод equals по умолчанию просто сравнивает указатели на объекты, определяя, ссылаются ли они на один и тот же объект. Предположим, что метод hashCode мы написали по всем канонам, а именно — сгенерировали средствами IDE, и он будет возвращать одинаковые хэш-значения для логически одинаковых объектов. Очевидно, что тем самым мы уже определили некоторый механизм сравнения двух объектов. Следовательно, пример из предыдущего пункта по идее должен выполняться. Но мы по-прежнему не сможем найти наш объект в хэш-таблице. Хотя будем уже близки к этому, потому что как минимум найдем корзину хэш-таблицы, в которой объект будет лежать. Для успешного поиска объекта в хэш-таблице помимо сравнения хэш-значений ключа используется также определение логического равенства ключа с искомым объектом. Т. е. без переопределения метода equals никак не получится обойтись. Общий алгоритм определения hashCode Здесь, мне кажется, вообще не стоит сильно переживать и выполнить генерацию метода в своей любимой IDE. Потому что все эти смещения битов вправо, влево в поиске золотого сечения, т. е. нормального распределения — это для совсем упоротых чуваков. Лично я сомневаюсь, что смогу сделать лучше и быстрее, чем та же Idea. Вместо заключения Таким образом, мы видим, что методы equals и hashCode играют четко определенную роль в языке Java и предназначены для получения характеристики логического равенства двух объектов. В случае с методом equals это имеет прямое отношение к сравнению объектов, в случае с hashCode косвенное, когда необходимо, скажем так, определить примерное расположение объекта в хэш-таблицах или подобных структурах данных с целью увеличения скорости поиска объекта. Помимо контрактов equals и hashCode имеется еще одно требование, относящееся к сравнению объектов. Это согласованность метода compareTo интерфейса Comparable с методом equals. Данное требование обязывает разработчика всегда возвращать x.equals(y) == true, когда x.compareTo(y) == 0. Т. е. мы видим, что логическое сравнение двух объектов не должно противоречить нигде в приложении и всегда быть согласованным.
Когда применять интерфейс логичнее, а когда абстрактный класс?
Для начала, рассмотрим основные отличия между интерфейсом и абстрактным классом. Для этого разобьем сравнение на несколько категорий и представим отличия в виде таблицы: Критерии сравнения Интерфейс Абстрактный класс Наследование классов Класс может реализовать (implement) множество интерфейсов Класс может наследовать только один абстрактный класс. Поля данных Интерфейс может содержать только общедоступные константы (public final static long SOME_CONST = 1) Абстрактный класс может содержать любые поля: статические и экземплярные, константы, private/protected/public Модификаторы доступа методов Методы в интерфейсе могут иметь модификаторы только public и abstract. По-умолчанию они уже public abstract К неабстрактным членам класса применимы любые модификаторы. Однако абстрактные методы (с модификатором abstract) могут иметь либо public, либо protected модификатор. Иными словами абстрактные методы не могут быть приватными. Реализация объявленных методов Интерфейс не может содержать никакой реализации методов. Абстрактный класс допускает реализацию методов Описание конструктора Интерфейс не может содержать никаких конструкторов В абстрактном классе можно описать конструктор (или несколько конструкторов). Взяв во внимание основные различия между интерфейсом и абстрактным классом, которые представлены выше, мы можем перейти к раскрытию основного вопроса этой статьи, а именно: в каких случаях уместно использовать абстрактный класс и интерфейс? 1) Абстрактный класс уместно использовать, если вы собираетесь использовать наследование, которое будет обеспечивать общую структуру. 2) Абстрактный класс также уместно использовать, если вы хотите объявить приватные экземпляры. В интерфейсах, все методы должны быть публичными. 3) Если вы считаете, что в будущем вам понадобится прибегать к добавлению новых методов, тогда абстрактный класс - лучший вариант. В случае интерфейса вам необходимо будет объявлять новый метод в каждом классе, который реализует ваш интерфейс. 4) Интерфейсы - хороший выбор, если вам известно, что API не будет меняться некоторое время. 5) Также, для реализации «множественного наследования» кроме интерфейсов вы не найдете альтернативных решений. Абстрактный класс в отличии от интерфейса описывает больше структуру. Обычно он определяет общее представление и обеспечивает пользователя абстрактным классом некоторым инструментарием для работы с ним, но уже в классах-потомках. Суть заключается в следующем: код, использующий абстрактный класс должен использовать его, как фундамент. В свою очередь интерфейс позволяет сторонним разработчикам реализовать методы вашего интерфейса на свое усмотрение. Им не нужно брать во внимание особенности вашего кода, т.к. для них будет стоять иная задача: написать свою реализацию интерфейса. Им также следует брать во внимание еще один недостаток использования интерфейсов: каждый метод интерфейса - public, что может нарушать инкапсуляцию. Одновременное использование интерфейсов и абстрактных классов также возможно. Однако, здесь стоит помнить, что вызов методов через имя в интерфейсе будет немного медленнее чем вызов через имя в абстрактном классе. Но это может быть очень неудобным для сторонних программистов, которые станут использовать ваш код и уже создали свой фундамент. Полезные ресурсы "Java 2. Том I. Библиотека профессионала." Г. Корнелл, Кей Хорстманн; "Java. Полное руководство." Г. Шилдт; "Effective Java, Second edition" Joshua Bloch; Интерфейсы vs. классы - http://habrahabr.ru/post/30444/ http://www.quizful.net/post/razlichie_v_primenenii_interfeysov_i_abstraktnih_klassov
как скомпилировать и запустить класс используя консоль java
Если в программе несколько файлов HelloWorld.java package com.qwertovsky.helloworld; Calculator.java package com.qwertovsky.helloworld; Adder.java package com.qwertovsky.helloworld.operation; Компилируем javac -d bin src/com/qwertovsky/helloworld/HelloWorld.java src\com\qwertovsky\helloworld\HelloWorld.java:9: cannot find symbol symbol : class Calculator location: class com.qwertovsky.helloworld.HelloWorld Calculator calc=new Calculator(); ^ src\com\qwertovsky\helloworld\HelloWorld.java:9: cannot find symbol symbol : class Calculator location: class com.qwertovsky.helloworld.HelloWorld Calculator calc=new Calculator(); ^ 2 errors Ошибка возникла из-за того, что для компиляции нужны файлы с исходными кодами классов, которые используются (класс Calculator). Надо указать компилятору каталог с файлами с помощью ключа -sourcepath. Компилируем javac -sourcepath ./src -d bin src/com/qwertovsky/helloworld/HelloWorld.java Запускаем java -classpath ./bin com.qwertovsky.helloworld.HelloWorld Hello Word 2+3=5 Если удивляет результат Есть возможность запустить отладчик. Для этого существует jdb. Сначала компилируем с ключом -g, чтобы у отладчика была информация. javac -g -sourcepath ./src -d bin src/com/qwertovsky/helloworld/HelloWorld.java Запускаем отладчик jdb -classpath bin -sourcepath src com.qwertovsky.helloworld.HelloWorld Initializing jdb ... >
Что означает термин «пакетный» уровень доступа (package access)?
Из Java документация Если класс не имеет модификатора (по умолчанию, также называемого package-private), он виден только в пределах его собственного пакета (пакеты называются группами связанных классов - вы узнаете о них на более позднем уроке.) Access Levels Modifier Class Package Subclass World ----------------------------------------------------- public Y Y Y Y protected Y Y Y N (Default) Y Y N N private Y N N N Это есть уровень доступа в пределах пакета. Его еще называют доступ по умолчанию. Такой уровень доступа устанавливается, если явным образом не задан спецификатор доступа (private, protected, public). При пакетном уровне доступа член класса есть доступен другим классам текущего пакета. Для классов, которые реализованы в других пакетах, данный член класса считается скрытым или приватным (private). ***************EXAMPLE**************** В примере объявляется проект с именем DemoPackages. В проекте созданы 2 пакета с именами PackageA, PackageB. В пакете PackageA реализована библиотека из двух классов, которые имеют имена ClassA1 (размещается в модуле ClassA1.java) и ClassA2 (размещается в модуле ClassA2.java). Начальная структура проекта изображена на рисунке 1. Рис. 1. Фрагмент окна Package Explorer. Структура пакета DemoPackages Ниже приведены программные коды модулей (файлов) ClassA1.java, ClassA2.java, ClassB1.java, ClassB2.java которые соответствуют классам ClassA1, ClassA2, ClassB1, ClassB2. Текст модуля ClassA1.java. В классе корректно создается объект класса ClassA2. Это логично, поскольку классы ClassA1 и ClassA2 объявлены в одном пакете и имеют доступ в пределах пакета. Однако, попытка создать объект класса ClassB1 из другого пакета вызовет ошибку компиляции. package PackageA; class ClassA1 // нет явно заданного спецификатора public, доступ по умолчанию { int a1; public static void main(String[] args) { // доступ к классу Class2 из класса Class1 - корректно, классы в одном пакете ClassA2 cA2 = new ClassA2(); cA2.a2 = 109; // этот код не будет компилироваться: классы ClassA1 и ClassB1 в разных пакетах // PackageB.ClassB1 cB1 = new PackageB.ClassB1(); // ошибка! } } Текст модуля ClassA2.java. В классе корректно создается объект класса ClassA1, так как классы объявляются в одном пакете. Ну и конечно, доступиться к классу ClassB1 по сокращенному имени не выйдет, поскольку этот класс реализован в другом пакете (PackageB). package PackageA; class ClassA2 // нету явно заданного спецификатора public, доступ по умолчанию { int a2; public static void main(String[] args) { // доступ из класса ClassA2 к классу ClassA1 - работает, оба класса в одном пакете ClassA1 cA1 = new ClassA1(); cA1.a1 = 17; // PackageB.ClassB1 cB1 = new PackageB.ClassB1(); // а это ошибка компиляции, классы в разных пакетах } } Текст модуля ClassB1.java. В модуле корректно создается объект класса ClassB2, так как классы ClassB1, ClassB2 находятся в одном пакете PackageB, доступ к которым осуществляется по умолчанию. Попытка создать объект класса ClassA1 есть неудачной, поскольку класс ClassA1 объявлен в другом пакете и для него не задан модификатора доступа public. package PackageB; // нет явно заданного спецификатора public, доступ по умолчанию (в границах пакета) class ClassB1 { double b1 = 12.44; // внутренняя переменная public static void main(String[] args) { // доступ к классу ClassB2 из класса ClassB1 ClassB2 cB2 = new ClassB2(); cB2.b2 = 230; // здесь возникнет ошибка компиляции: // PackageA.ClassA1 cA1 = new PackageA.ClassA1(); // ошибка, ClassA1 объявляется в другом пакете } } Текст модуля ClassB2.java. В классе корректно инициализируется переменная класса ClassB1 так как классы ClassB1 и ClassB2 находятся в одном пакете. package PackageB; // нет явно заданного спецификатора public, доступ по умолчанию (в границах пакета) class ClassB2 { int b2; // внутренняя переменная // инициализация работает, так как классы в одном пакете PackageB ClassB1 cB1 = new ClassB1(); }
41. Опишите иерархию исключений.
Исключение — это проблема(ошибка) возникающая во время выполнения программы. Исключения могут возникать во многих случаях, например: Пользователь ввел некорректные данные. Файл, к которому обращается программа, не найден. Сетевое соединение с сервером было утеряно во время передачи данных. И т.д. Все исключения в Java являются объектами. Поэтому они могут порождаться не только автоматически при возникновении исключительной ситуации, но и создаваться самим разработчиком. Исключения делятся на несколько классов, но все они имеют общего предка — класс Throwable. Его потомками являются подклассы Exception и Error. Исключения (Exceptions) являются результатом проблем в программе, которые в принципе решаемые и предсказуемые. Например, произошло деление на ноль в целых числах. Ошибки (Errors) представляют собой более серьёзные проблемы, которые, согласно спецификации Java, не следует пытаться обрабатывать в собственной программе, поскольку они связаны с проблемами уровня JVM. Например, исключения такого рода возникают, если закончилась память, доступная виртуальной машине. Программа дополнительную память всё равно не сможет обеспечить для JVM. В Java все исключения делятся на два типа: контролируемые исключения (checked) и неконтролируемые исключения (unchecked), к которым относятся ошибки (Errors) и исключения времени выполнения (RuntimeExceptions, потомок класса Exception). Контролируемые исключения представляют собой ошибки, которые можно и нужно обрабатывать в программе, к этому типу относятся все потомки класса Exception (но не RuntimeException).
45. Для чего нужны собственные исключения? Как его создать.
Как выбросить свое исключение Разумеется, создатели Java не в состоянии предусмотреть все исключительные ситуации, которые могут возникнуть в программах. Программ в мире слишком много, и они слишком разные. Но это не страшно, поскольку при необходимости ты можешь создавать собственные исключения. Это делается очень легко. Тебе достаточно создать собственный класс. Его название должно заканчиваться на "Exception". Компилятору это не нужно, но читающим твой код программистам сразу будет понятно, что это класс-исключение. Кроме того, нужно указать что класс происходит от класса Exception. Это уже нужно для компилятора и корректной работы.
Как работает метод substring() класса String?
Как и в других языках программирования, строки в Java являются последовательностью символов. Этот класс больше похож на служебный класс для работы с этой последовательностью. Последовательность символов обеспечивается следующей переменной: /** The value is used for character storage. */ /** Значение используется для хранения символов */ private final char value[]; Для доступа к этому массиву в различных сценариях используются следующие переменные /** The offset is the first index of the storage that is used. */ /** Смещение - это первый индекс используемого хранилища. */ private final int offset; /** The count is the number of characters in the String. */ /** Счет - это количество символов в строке. */ private final int count; Каждый раз, когда мы создаем подстроку от существующего экземпляра строки, метод substring() только устанавливает новые значения переменных offset и count. Внутренний массив символов не изменяется. Это возможный источник утечки памяти, если метод substring() использовать неосторожно: Первоначальное значение value[] не изменяется. Поэтому если вы создадите строку длиной 10000 символов и создадите 100 подстрок с 5-10 символами в каждой, все 101 объекты будут содержать один и тот же символьный массив длиной 10000 символов. Это без сомнения расточительство памяти. Этого можно избежать, изменив код следующим образом: заменить original.substring(beginIndex) на new String(original.substring(beginIndex)), где original - исходная строка. Примечание переводчика: я затрудняюсь сказать к какой версии Java это применимо, но на данный момент в Java 7 этот пункт статьи не актуален. Метод substring() вызывает конструктор класса new String(value, beginIndex, subLen), который в свою очередь обращается к методу Arrays.copyOfRange(value, offset, offset+count). Это значит, что у нас будет каждый раз новое значение переменной value[], содержащее наше новое количество символов.
25. Пакеты в java. Зачем применяются? Принцип именования пакетов.
Как правило, в Java классы объединяются в пакеты. Пакеты позволяют организовать классы логически в наборы. По умолчанию java уже имеет ряд встроенных пакетов, например, java.lang, java.util, java.io и т.д. Кроме того, пакеты могут иметь вложенные пакеты. Организация классов в виде пакетов позволяет избежать конфликта имен между классами. Ведь нередки ситуации, когда разработчики называют свои классы одинаковыми именами. Принадлежность к пакету позволяет гарантировать однозначность имен. Чтобы указать, что класс принадлежит определенному пакету, надо использовать директиву package, после которой указывается имя пакета: Существует общепринятая схема, где первая часть имени пакета должна состоять из перевёрнутого доменного имени разработчика класса. Так как доменные имена в интернете уникальны, соблюдение этого правила обеспечивает уникальность имён пакетов и предотврати конфликты. Если у вас нет собственного доменного имени, то придумайте свою уникальную комбинацию с малой вероятностью повторения. http://developer.alexanderklimov.ru/android/java/package.php
как реализуется класс string какие поля там есть
Класс java.lang.String состоит из трех final полей: массив символов, длина строки и сдвиг в массиве, с помощью которого реализуется метод substring. Так же в этом классе есть поле, в котором хранится посчитанный хэшкод, после первого вызова метода hashCode. String Строка — объект, что представляет последовательность символов. Для создания и манипулирования строками Java платформа предоставляет общедоступный финальный (не может иметь подклассов) класс java.lang.String. Данный класс является неизменяемым (immutable) — созданный объект класса String не может быть изменен. Можно подумать что методы имеют право изменять этот объект, но это неверно. Методы могут только создавать и возвращать новые строки, в которых хранится результат операции. Неизменяемость строк предоставляет ряд возможностей: использование строк в многопоточных средах (String является потокобезопасным (thread-safe) ) использование String Pool (это коллекция ссылок на String объекты, используется для оптимизации памяти) использование строк в качестве ключей в HashMap (ключ рекомендуется делать неизменяемым) Создание Мы можем создать объект класса String несколькими способами: 1. Используя строковые литералы: String habr = "habrahabr"; Строковый литерал — последовательность символов заключенных в двойные кавычки. Важно понимать, что всегда когда вы используете строковой литерал компилятор создает объект со значением этого литерала: System.out.print("habrahabr"); // создали объект и вывели его значение 2. С помощью конструкторов: String habr = "habrahabr"; char[] habrAsArrayOfChars = {'h', 'a', 'b', 'r', 'a', 'h', 'a', 'b', 'r'}; byte[] habrAsArrayOfBytes = {104, 97, 98, 114, 97, 104, 97, 98, 114}; String first = new String(); String second = new String(habr); Если копия строки не требуется явно, использование этих конструкторов нежелательно и в них нет необходимости, так как строки являются неизменными. Постоянное строительство новых объектов таким способом может привести к снижению производительности. Их лучше заменить на аналогичные инициализации с помощью строковых литералов. String third = new String(habrAsArrayOfChars); // "habrahabr" String fourth = new String(habrAsArrayOfChars, 0, 4); // "habr" Конструкторы могут формировать объект строки с помощью массива символов. Происходит копирование массива, для этого используются статические методы copyOf и copyOfRange (копирование всего массива и его части (если указаны 2-й и 3-й параметр конструктора) соответственно) класса Arrays, которые в свою очередь используют платформо-зависимую реализацию System.arraycopy. String fifth = new String(habrAsArrayOfBytes, Charset.forName("UTF-16BE")); // кодировка нам явно не подходит "桡扲慨慢�" Можно также создать объект строки с помощью массива байтов. Дополнительно можно передать параметр класса Charset, что будет отвечать за кодировку. Происходит декодирование массива с помощью указанной кодировки (если не указано — используется Charset.defaultCharset(), который зависит от кодировки операционной системы) и, далее, полученный массив символов копируется в значение объекта. String sixth = new String(new StringBuffer(habr)); String seventh = new String(new StringBuilder(habr)); Ну и наконец-то конструкторы использующие объекты StringBuffer и StringBuilder, их значения (getValue()) и длину (length()) для создания объекта строки. С этими классами мы познакомимся чуть позже. Приведены примеры наиболее часто используемых конструкторов класса String, на самом деле их пятнадцать (два из которых помечены как deprecated). Длина Важной частью каждой строки есть ее длина. Узнать ее можно обратившись к объекту String с помощью метода доступа (accessor method) length(), который возвращает количество символов в строке, например: public static void main(String[] args) { String habr = "habrahabr"; // получить длину строки int length = habr.length(); // теперь можно узнать есть ли символ символ 'h' в "habrahabr" char searchChar = 'h'; boolean isFound = false; for (int i = 0; i < length; ++i) { if (habr.charAt(i) == searchChar) { isFound = true; break; // первое вхождение } } System.out.println(message(isFound)); // Your char had been found! // ой, забыл, есть же метод indexOf System.out.println(message(habr.indexOf(searchChar) != -1)); // Your char had been found! } private static String message(boolean b) { return "Your char had" + (b ? " " : "n't ") + "been found!"; } Конкатенация Конкатенация — операция объединения строк, что возвращает новую строку, что есть результатом объединения второй строки с окончанием первой. Операция для объекта String может быть выполнена двумя способами: 1. Метод concat String javaHub = "habrhabr".concat(".ru").concat("/hub").concat("/java"); System.out.println(javaHub); // получим "habrhabr.ru/hub/java" // перепишем наш метод используя concat private static String message(boolean b) { return "Your char had".concat(b ? " " : "n't ").concat("been found!"); } Важно понимать, что метод concat не изменяет строку, а лишь создает новую как результат слияния текущей и переданной в качестве параметра. Да, метод возвращает новый объект String, поэтому возможны такие длинные «цепочки». 2. Перегруженные операторы "+" и "+=" String habr = "habra" + "habr"; // "habrahabr" habr += ".ru"; // "habrahabr.ru" Это одни с немногих перегруженных операторов в Java — язык не позволяет перегружать операции для объектов пользовательских классов. Оператор "+" не использует метод concat, тут используется следующий механизм: String habra = "habra"; String habr = "habr"; // все просто и красиво String habrahabr = habra + habr; // а на самом деле String habrahabr = new StringBuilder()).append(habra).append(habr).toString(); // может быть использован StringBuffer Используйте метод concat, если слияние нужно провести только один раз, для остальных случаев рекомендовано использовать или оператор "+" или StringBuffer / StringBuilder. Также стоит отметить, что получить NPE (NullPointerException), если один с операндов равен null, невозможно с помощью оператора "+" или "+=", чего не скажешь о методе concat, например: String string = null; string += " habrahabr"; // null преобразуется в "null", в результате "null habrahabr" string = null; string.concat("s"); // логично что NullPointerException Форматирование Класс String предоставляет возможность создания форматированных строк. За это отвечает статический метод format, например: String formatString = "We are printing double variable (%f), string ('%s') and integer variable (%d)."; System.out.println(String.format(formatString, 2.3, "habr", 10)); // We are printing double variable (2.300000), string ('habr') and integer variable (10). Методы Благодаря множеству методов предоставляется возможность манипулирования строкой и ее символами. Описывать их здесь нет смысла, потому что Oracle имеет хорошие статьи о манипулировании и сравнении строк. Также у вас под рукой всегда есть их документация. Хотелось отметить новый статический метод join, который появился в Java 8. Теперь мы можем удобно объединять несколько строк в одну используя разделитель (был добавлен класс java.lang.StringJoiner, что за него отвечает), например: String hello = "Hello"; String habr = "habrahabr"; String delimiter = ", "; System.out.println(String.join(delimiter, hello, habr)); // или так System.out.println(String.join(delimiter, new ArrayList<CharSequence>(Arrays.asList(hello, habr)))); // в обоих случаях "Hello, habrahabr" Это не единственное изменение класса в Java 8. Oracle сообщает о улучшении производительности в конструкторе String(byte[], *) и методе getBytes(). Преобразование 1. Число в строку int integerVariable = 10; String first = integerVariable + ""; // конкатенация с пустой строкой String second = String.valueOf(integerVariable); // вызов статического метода valueOf класса String String third = Integer.toString(integerVariable); // вызов метода toString класса-обертки 2. Строку в число String string = "10"; int first = Integer.parseInt(string); /* получаем примитивный тип (primitive type) используя метод parseXхх нужного класса-обертки, где Xxx - имя примитива с заглавной буквы (например parseInt) */ int second = Integer.valueOf(string); // получаем объект wrapper класса и автоматически распаковываем StringBuffer Строки являются неизменными, поэтому частая их модификация приводит к созданию новых объектов, что в свою очередь расходует драгоценную память. Для решения этой проблемы был создан класс java.lang.StringBuffer, который позволяет более эффективно работать над модификацией строки. Класс является mutable, то есть изменяемым — используйте его, если Вы хотите изменять содержимое строки. StringBuffer может быть использован в многопоточных средах, так как все необходимые методы являются синхронизированными. Создание Существует четыре способа создания объекта класса StringBuffer. Каждый объект имеет свою вместимость (capacity), что отвечает за длину внутреннего буфера. Если длина строки, что хранится в внутреннем буфере, не превышает размер этого буфера (capacity), то нет необходимости выделять новый массив буфера. Если же буфер переполняется — он автоматически становиться больше. StringBuffer firstBuffer = new StringBuffer(); // capacity = 16 StringBuffer secondBuffer = new StringBuffer("habrahabr"); // capacity = str.length() + 16 StringBuffer thirdBuffer = new StringBuffer(secondBuffer); // параметр - любой класс, что реализует CharSequence StringBuffer fourthBuffer = new StringBuffer(50); // передаем capacity Модификация В большинстве случаев мы используем StringBuffer для многократного выполнения операций добавления (append), вставки (insert) и удаления (delete) подстрок. Тут все очень просто, например: String domain = ".ru"; // создадим буфер с помощью String объекта StringBuffer buffer = new StringBuffer("habrahabr"); // "habrahabr" // вставим домен в конец buffer.append(domain); // "habrahabr.ru" // удалим домен buffer.delete(buffer.length() - domain.length(), buffer.length()); // "habrahabr" // вставим домен в конец на этот раз используя insert buffer.insert(buffer.length(), domain); // "habrahabr.ru" Все остальные методы для работы с StringBuffer можно посмотреть в документации. StringBuilder StringBuilder — класс, что представляет изменяемую последовательность символов. Класс был введен в Java 5 и имеет полностью идентичный API с StringBuffer. Единственное отличие — StringBuilder не синхронизирован. Это означает, что его использование в многопоточных средах есть нежелательным. Следовательно, если вы работаете с многопоточностью, Вам идеально подходит StringBuffer, иначе используйте StringBuilder, который работает намного быстрее в большинстве реализаций. Напишем небольшой тест для сравнения скорости работы этих двух классов: public class Test { public static void main(String[] args) { try { test(new StringBuffer("")); // StringBuffer: 35117ms. test(new StringBuilder("")); // StringBuilder: 3358ms. } catch (java.io.IOException e) { System.err.println(e.getMessage()); } } private static void test(Appendable obj) throws java.io.IOException { // узнаем текущее время до теста long before = System.currentTimeMillis(); for (int i = 0; i++ < 1e9; ) { obj.append(""); } // узнаем текущее время после теста long after = System.currentTimeMillis(); // выводим результат System.out.println(obj.getClass().getSimpleName() + ": " + (after - before) + "ms."); } } Спасибо за внимание. Надеюсь статья поможет узнать что-то новое и натолкнет на удаление всех пробелов в этих вопросах. Все дополнения, уточнения и критика приветствуются.
в чем разница между созданием строки как new string() и литералом (при помощи двойных кавычек)
Когда мы создаем строку используя new(), она создается в хипе и также добавляется в пул строк, в то же время строка, созданная при помощи литерала, создается только в пуле строк. Когда один и тот же строковый литерал создан более одного раза, в памяти сохраняется только одна копия строки, только лишь один ее экземпляр (в нашем случае "abcd"). Это называется "интернирование строк". Все строковые константы, обрабатываемые на этапе компиляции, в Java интернируются автоматически. new String("text"); явно создает новый и ссылочно отдельный экземпляр объекта String; String s = "text"; может повторно использовать экземпляр из пула константных строк , если он доступен. Вы очень редко захотите использовать конструктор new String(anotherString). Из API: String(String original): Инициализирует вновь созданный объект String, чтобы он представлял ту же последовательность символов, что и аргумент; Другими словами, вновь созданная строка является копией строки аргумента. Если явная копия оригинала не нужна, использование этого конструктора необязательно, поскольку строки неизменяемы.
30. Чем отличается OutOfMemoryError от StackOverflowError. Как их избежать?
Короткий ответ: OutOfMemoryError связан с кучей. StackOverflowError связан со стеком Длинный ответ: Когда вы запускаете JVM вы определяете, сколько ОЗУ он может использовать для обработки. JVM делит это на определенные области памяти для целей обработки, два из них - Stack & Heap Если у вас есть большие объекты (или) ссылочные объекты в памяти, то вы увидите OutofMemoryError. Если у вас есть сильные ссылки на объекты, то GC не может очистить пространство памяти, выделенное для этого объекта. Когда JVM пытается выделить память для нового объекта и недостаточно свободного места, он выбрасывает OutofMemoryError потому что он не может выделить необходимый объем памяти. Как избежать: убедитесь, что ненужные объекты доступны для GC Все ваши локальные переменные и вызовы методов, относящиеся к данным, будут в стеке. Для каждого вызова метода будет создан один кадр стека, и локальные данные, а также данные, связанные с вызовом метода, будут помещены в кадр стека. Как только выполнение метода будет завершено, кадр стека будет удален. ONE WAY воспроизвести это, есть бесконечный цикл для вызова метода, вы увидите stackoverflow ошибки, потому что кадр стека будет заполнен методом данными для каждого вызова, но он не будет освобожден (удален). Как избежать: убедитесь, что вызовы методов заканчиваются (не в бесконечном цикле) Представьте, что у вас есть функция, подобная следующей public void f(int x) { return f(x + 1); } Когда вы вызовете это, вызов вызовет f снова и снова и снова. При каждом вызове в стек хранится бит информации. Поскольку размер стека ограничен, вы получите StackOverflowError. Теперь представьте следующий код: for (int i = 1; i > 0; i++) vector.add(new BigObject()); где BigObject - обычный объект Java. Как вы видите, цикл никогда не заканчивается. Каждое выделение выполняется в куче, поэтому оно будет заполнено BigObject, и вы получите OutOfMemoryError. Повторить: OutOfMemoryError вызывается при создании объектов StackOverflowError вызывается при вызове функций
61. Как работает метод yield()? Чем отличаются методы Thread.sleep() и Thread.yield()?
Метод yield() служит причиной того, что поток переходит из состояния работающий (running) в состояние работоспособный (runnable), давая возможность другим потокам активизироваться. Но следующий выбранный для запуска поток может и не быть другим. Метод sleep() вызывает засыпание текущего потока на заданное время, состояние изменяется с работающий (running) на ожидающий (waiting).
какие идентификаторы по умолчанию имеют поля интерфейса
Поскольку интерфейс не имеет прямого объекта, единственный способ доступа к ним - использовать класс/интерфейс и, следовательно, поэтому, если переменная интерфейса существует, она должна быть статической, иначе она не будет вообще доступна для внешнего мира, Теперь, поскольку он является статическим, он может содержать только одно значение, и любые его классы, которые его реализуют, могут изменить его, и, следовательно, все будет бесполезно. Следовательно, если вообще есть интерфейсная переменная, она будет неявно статичной, окончательной и, очевидно, публичной!!!
56. Что такое поток? Состояния потока.
Многопоточность Процессы - приложения со своим набором run-time ресурсов и собственной памятью. Взаимодействие через Inter Process Communication ресурсы. Можно запускать на нескольких компьютерах. Потоки - живут в одном процессе. Используют общую память(heap) и другие ресурсы приложения. Старт приложения - создание main потока. Потоки могут порождать другие потоки и взаимодействовать с ними. Поток Объект, у класса которого есть методы start() и run() После вызова метода start() будет выполнен run() Метод run() будет выполнен в стеке Операционная система Создает потоки Переключает потоки( context switch) API для уведомления потока( для того чтобы попросить что-то сделать с потоком) Mutex Сужение семафора Простейшие двоичные семафоры, которые могут находиться в одном из двух состояний - отмеченном или неотмеченном Critical Section Участок исполняемого кода программы, в котором производится доступ к общему ресурсу (данным или устройству), который не должен быть одновременно использован более чем одним потоком исполнения. Monitor Механизм взаимодействия и синхронизации процессов Высокоуровневая конструкция, которая состоит из mutex-а и массива ожидающих очереди потоков. У монитора должен быть механизм остановки потока и сигнализации о доступности продолжения работы. Semaphore Объект, ограничивающий количество потоков, которые могут войти в заданный участок кодв(Э.Дейкстра) Init(n); Счетчик = n; Enter() // ждать пока счетчик станет больше 0, после этого уменьшить счетчик на еденицу Leave() //увеличить счетчик на еденицу Lock Блокировка Это механизм синхронизации, позволяющий обеспечить исключительный доступ к разделяемому ресурсу между несколькими потоками. Мягкая блокировка(Java) - каждый поток пытается получить блокировку перед доступом к соответствующему разделяемому ресурсу. Обязательная блокировка - попытка несанкционированного доступа к заблокированному ресурсу будет прервана, через создание исключения. Аппаратная поддержка compare and swap Взаимодействие потоков У потоков общий heap Можно передать в два потока ссылку на объект Потоки смогут менять общий объект и взаимодействовать через него
69. Для чего применяется volatile? Пакет java.util.concurrent.atomic.
Модификатор volatile накладывает некоторые дополнительные условия на чтение/запись переменной. Важно понять две вещи о volatile переменных: Операции чтения/записи volatile переменной являются атомарными. Результат операции записи значения в volatile переменную одним потоком, становится виден всем другим потокам, которые используют эту переменную для чтения из нее значения. Кажется, что для человека, задающего вопрос вроде вашего, достаточно знать эти два момента. Определение переменной с ключевым словом volatile означает, что значение этой переменной может изменяться другими потоками. Чтобы понять, что делает volatile, полезно разобраться, как потоки обрабатывают обычные переменные. В целях повышения производительности спецификация языка Java допускает сохранение в JRE локальной копии переменной для каждого потока, который на нее ссылается. Такие "локальные" копии переменных напоминают кэш и помогают потоку избежать обращения к главной памяти каждый раз, когда требуется получить значение переменной. При запуске двух потоков один из них считывает переменную A как 5, а второй ― как 10. Если значение переменной А изменилось с 5 на 10, то первый поток не узнает об изменении и будет хранить неправильное значение A. Но если переменная А помечена как volatile, то когда бы поток не считывал значение A, он будет обращаться к главной копии A и считывать ее текущее значение. Локальный кэш потока имеет смысл в том случае, если переменные в ваших приложениях не будут изменяться извне. Если переменная объявлена как volatile, это означает, что она может изменяться разными потоками. Естественно ожидать, что JRE обеспечит ту или иную форму синхронизации таких volatile-переменных. JRE действительно неявно обеспечивает синхронизацию при доступе к volatile-переменным, но с одной очень большой оговоркой: чтение volatile-переменной и запись в volatile-переменную синхронизированы, а неатомарные операции ― нет.
67. Написать deadlock, придумать примеры с использованием synchronized, AtomicInteger
Наличие дедлока в двух факторах: 1) наличие 2-х объектов локов 2) последовательность захвата разная, т.е final Object lock1 = new Object(); final Object lock2 = new Object(); void method1() { synchronized(lock1) { synchronized(lock2) { //doSomething() } } } void method1() { synchronized(lock2) { synchronized(lock1) { //doSomething() } } } Здесь дедлок не избежен. Что делать? 1) Использовать только один lock вместо двух 2) Если не возможно по ряду причин выполнить пункт 1), то использовать такую схему захвата void method2() { if (System.identityHashCode(lock1)>=identityHashCode(lock2)) { synchronized(lock1) { synchronized(lock2) { //doSomething() } } } else { synchronized(lock2) { synchronized(lock1) { //doSomething() } } } } 3) Использовать класс ReentrantLock, где устанавливать время Как правило на собеседованиях задают вопрос именно "как избежать дедлока". Конечно можно сказать, что неиспользование многопоточности избавит вас от появления дедлока, но это глупо, поэтому вопрос не совсем корректен, хотя встречается часто. Наиболее грамотно будет ответить, за счет чего будет уменьшен шанс его появления. Ну так и сказали бы, что за счет разумного использование synchronized, volatile, монитора (wait(), notify(), notifyAll()), а если копать глубже, то используя классы java.utils.concurrent: вместо обычных коллекций - многопоточные варианты (ConcurrentHashMap, например); если нужен более сложный способ синхронизации потоков - различные CyclicBarrier, CountDownLatch. Не будет лишним сказать, что в теории бывают lockfree алгоритмы (но мы-то знаем... ^_^'). ответить # Vasilyich15.11.2013 | 10:52:50 Обычно достаточно согласованного между потоками порядка захвата ресурсов, при котором все потоки захватывают ресурсы в одном и том же порядке. С появлением семафоров (lock, tryLock()) это может реализовываться очень просто. A: alphonse.bow(gaston) — получает лок alphonse G: gaston.bow(alphonse) — получает лок gaston G: пытается вызвать alphonse.bowBack(gaston), но блокируется, ожидая лока alphonse A: пытается вызвать gaston.bowBack(alphonse), но блокируется, ожидая лока gaston Что такое Deadlock? - 2 В этом случае обе нити заблокированы и каждая ожидает, что другая отдаст лок. Но ни одна это не сделает, потому что для этого нужно завершить свой метод, а он заблокирован другой нитью. Поэтому они застряли на месте, случился deadlock.
правила переопределения метода object.equals()
Общий алгоритм определения equals Проверить на равенство ссылки объектов this и параметра метода o. if (this == o) return true; Проверить, определена ли ссылка o, т. е. является ли она null. Если в дальнейшем при сравнении типов объектов будет использоваться оператор instanceof, этот пункт можно пропустить, т. к. этот параметр возвращает false в данном случае null instanceof Object. Сравнить типы объектов this и o с помощью оператора instanceof или метода getClass(), руководствуясь описанием выше и собственным чутьем. Если метод equals переопределяется в подклассе, не забудьте сделать вызов super.equals(o) Выполнить преобразование типа параметра o к требуемому классу. Выполнить сравнение всех значимых полей объектов: для примитивных типов (кроме float и double), используя оператор == для ссылочных полей необходимо вызвать их метод equals для массивов можно воспользоваться перебором по циклу, либо методом Arrays.equals() для типов float и double необходимо использовать методы сравнения соответствующих оберточных классов Float.compare() и Double.compare() И, наконец, ответить на три вопроса: является ли реализованный метод симметричным? Транзитивным? Согласованным? Два других принципа (рефлексивность и определенность), как правило, выполняются автоматически.
что такое enum java
Перечисление - это класс Объявляя enum мы неявно создаем класс производный от java.lang.Enum. Условно конструкция enum Season { ... } эквивалентна class Season extends java.lang.Enum { ... }. И хотя явным образом наследоваться от java.lang.Enum нам не позволяет компилятор, все же в том, что enum наследуется, легко убедиться с помощью reflection: System.out.println(Season.class.getSuperclass()); На консоль будет выведено: class java.lang.Enum Собственно наследование за нас автоматически выполняет компилятор Java. Далее давайте условимся называть класс, созданный компилятором для реализации перечисления - enum-классом, а возможные значения перечисляемого типа - элементами enum-a. Элементы перечисления - экземпляры enum-класса, доступные статически Элементы enum Season (WINTER, SPRING и т.д.) - это статически доступные экземпляры enum-класса Season. Их статическая доступность позволяет нам выполнять сравнение с помощью оператора сравнения ссылок ==. Пример: Season season = Season.SUMMER; if (season == Season.AUTUMN) season = Season.WINTER; Название и порядковый номер элемента enum Как уже было сказано ранее любой enum-класс наследует java.lang.Enum, который содержит ряд методов полезных для всех перечислений. Пример: Season season = Season.WINTER; System.out.println("season.name()=" + season.name() + " season.toString()=" + season.toString() + " season.ordinal()=" + season.ordinal()); Будет выведено: season.name()=WINTER season.toString()=WINTER season.ordinal()=0 Здесь показано использования методов name(), toString() и ordinal(). Семантика методов - очевидна. Следует обратить внимание, что данные методы enum-класс наследует из класса java.lang.Enum Получение елемента enum по строковому представлению его имени Довольно часто возникает задача получить элемент enum по его строковому представлению. Для этих целей в каждом enum-классе компилятор автоматически создает специальный статический метод: public static EnumClass valueOf(String name), который возвращает элемент перечисления EnumClass с названием, равным name. Пример использования: String name = "WINTER"; Season season = Season.valueOf(name); В результате выполнения кода переменная season будет равна Season.WINTER. Cледует обратить внимание, что если элемент не будет найден, то будет выброшен IllegalArgumentException, а в случае, если name равен null - NullPointerException. Об этом, кстати, часто забывают. Почему-то многие твердо уверенны, что если функция принимает один аргумент и при некоторых услових выбрасывает IllegalArgumentException, то при передачи туда null, также будет неприменно выброшен IllegalArgumentException. Но это не относится к делу. Продолжим. Получение всех элементов перечисления Иногда необходимо получить список всех элементов enum-класса во время выполнения. Для этих целей в каждом enum-классе компилятор создает метод: public static EnumClass[] values(). Пример использования: System.out.println(Arrays.toString(Season.values())); Получим вывод: [WINTER, SPRING, SUMMER, AUTUMN] Обратите внимание, что ни метод valueOf(), ни метод values() не определен в классе java.lang.Enum. Вместо этого они автоматически добавляются компилятором на этапе компиляции enum-класса. Добавляем свои методы в enum-класс У Вас есть возможность добавлять собственные методы как в enum-класс, так и в его элементы: enum Direction { UP, DOWN; public Direction opposite() { return this == UP ? DOWN : UP; } } То же, но с полиморфизмом: enum Direction { UP { public Direction opposite() { return DOWN; } }, DOWN { public Direction opposite() { return UP; } }; public abstract Direction opposite(); } Последний пример демонстрирует использование наследования в enum. Об этом - далее. Наследование в enum С помощью enum в Java можно реализовать иерархию классов, объекты которой создаются в единственном экземпляре и доступны статически. При этом элементы enum могут содержать собственные конструкторы. Приведем пример: enum Type { INT(true) { public Object parse(String string) { return Integer.valueOf(string); } }, INTEGER(false) { public Object parse(String string) { return Integer.valueOf(string); } }, STRING(false) { public Object parse(String string) { return string; } }; boolean primitive; Type(boolean primitive) { this.primitive = primitive; } public boolean isPrimitive() { return primitive; } public abstract Object parse(String string); } Здесь объявляется перечисление Type с тремя элементами INT, INTEGER и STRING. Компилятор создаст следующие классы и объекты: Type - класс производный от java.lang.Enum INT - объект 1-го класса производного от Type INTEGER - объект 2-го класса производного от Type STRING - объект 3-го класса производного от Type Три производных класса будут созданы с полиморфным методом Object parse(String) и конструктором Type(..., boolean) При этом объекты классов INT, INTEGER и STRING существуют в единственном экземпляре и доступны статически. В этом можно убедится: System.out.println(Type.class); System.out.println(Type.INT.getClass() + " " + Type.INT.getClass().getSuperclass()); System.out.println(Type.INTEGER.getClass() + " " + Type.INTEGER.getClass().getSuperclass()); System.out.println(Type.STRING.getClass() + " " + Type.STRING.getClass().getSuperclass()); Получим вывод: class Type class Type$1 class Type class Type$2 class Type class Type$3 class Type Видно, что компилятор создал класс Type и 3 nested класса, производных от Type. Декомпилированный enum-class с наследованием В подтверждение вышесказанному приведем еще результат декомпиляции перечисления Type из примера выше: abstract class Type extends Enum { public static Type[] values() { return (Type[]) $VALUES.clone(); } public static Type valueOf(String name) { return (Type) Enum.valueOf(t / T$Type, name); } public boolean isPrimitive() { return primitive; } public abstract Object parse(String s); public static final Type INT; public static final Type INTEGER; public static final Type STRING; boolean primitive; private static final Type $VALUES[]; static { INT = new Type("INT", 0, true) { public Object parse(String string) { return Integer.valueOf(string); } }; INTEGER = new Type("INTEGER", 1, false) { public Object parse(String string) { return Integer.valueOf(string); } }; STRING = new Type("STRING", 2, false) { public Object parse(String string) { return string; } }; $VALUES = (new Type[]{ INT, INTEGER, STRING }); } private Type(String s, int i, boolean primitive) { super(s, i); this.primitive = primitive; } } Перечисления и параметрический полиморфизм У читателя может возниктуть вопрос: "почему вышеуказанное перечисление Type не использует генерики (generics) ?". Дело в том, что в Java использование генериков в enum запрещено. Так следующий пример не скомпилируется: enum Type<T> {} Дальнейшее изучение Для более глубокого понимания того, как работают перечисления в Java рекомендую ознакомиться с исходными кодами класса java.lang.Enum, а также воспользоваться декопмилятором Jad для изучения сгенерированного кода. Более того, изучение исходных кодов библиотеки Java абсолютно необходимо для понимания принципов работы многих механизмов в Java и полезно как эталон объектно-ориентированного дизайна. Также для закрепления знаний рекомендую пройти тесты: Тест знаний Java - Основы Тест знаний Java - Средний уровень
почему метод main public static void
Почему public? Метод main имеет модификатор доступа public, поэтому он может быть доступен везде и для любого объекта, который захочет использовать этот метод для запуска приложения. Тут я не говорю, что JDK/JRE имеют подобный повод, поскольку java.exe или javaw.exe (для windows) используют Java Native Interface (JNI) вызов для запуска метода, поэтому они могут вызвать его в любом случае, независимо от модификатора доступа. Почему static? Давайте предположим, что у нас метод main не статический. Теперь, для вызова любого метода вам необходим экземпляр класса. Верно? Java разрешает иметь перегруженные конструкторы, это мы все знаем. Тогда который из них должен быть использован, и откуда возьмутся параметры для перегруженного конструктора? Почему void? Нет применения для возвращаемого значения в виртуальной машине, которая фактически вызывает этот метод. Единственное, что приложение захочет сообщить вызвавшему процессу - это нормальное или ненормальное завершение. Это уже возможно используя System.exit(int). Не нулевое значение подразумевает ненормальное завершение, иначе все в порядке.
правила переопределения метода hashcode() можно ли возвращать константу
Правила переопределения hashCode Хэш — это некоторое число, генерируемое на основе объекта и описывающее его состояние в какой-то момент времени. Это число используется в Java преимущественно в хэш-таблицах, таких как HashMap. При этом хэш-функция получения числа на основе объекта должна быть реализована таким образом, чтобы обеспечить относительно равномерное распределение элементов по хэш-таблице. А также минимизировать вероятность появления коллизий, когда по разным ключам функция вернет одинаковое значение. Можно возвращать, но с потерей смысла всех коллекций использующих hashCode. HashMap превратится в обычный LinkedList, т.к. при данной реализации хеш-функця не равномерно распределяет значения , как следствие, при помещении объекта в HashMap все значения будут положены в один и тот же backet.
можно ли менять модификатор доступа метода?
При переопределении типа разрешается расширить видимость метода. Вот как это выглядит: Код на Java Описание class Cat { protected String getName() { return "Васька"; } } class Tiger extends Cat { public String getName() { return "Василий Тигранович"; } } Мы расширили видимость метода с protected до public. Использование Почему это «законно» public static void main(String[] args) { Cat cat = new Cat(); cat.getName(); } Все отлично. Тут мы даже не знаем, что в классе-наследнике видимость метода была расширена. public static void main(String[] args) { Tiger tiger = new Tiger(); tiger.getName(); } Тут вызывается метод, у которого расширили область видимости. Если бы этого сделать было нельзя, всегда можно было бы объявить метод в Tiger: public String getPublicName() { super.getName(); //вызов protected метода } Т.е. ни о каком нарушении безопасности и речи нет. public static void main(String[] args) { Cat catTiger = new Tiger(); catTiger.getName(); } Если все условия подходят для вызова метода базового типа (Cat), то они уж точно подойдут для вызова типа наследника (Tiger). Т.к. ограничения на вызов метода были ослаблены, а не усилены. ********* Возможно ли при переопределении (override) метода изменить: 1) Модификатор доступа - только расширять 2) Возвращаемый тип - изменять только в порядке наследования на расположенный ниже по иерархии 3) Тип аргумента или количество - нельзя, иначе это уже будет перегруженный метод 4) Имя аргументов - можно 5) Изменять порядок, количество или вовсе убрать секцию throws - нельзя сужать throws, а убрать вовсе можно ************ Есть такая важная вещь как принцип подстановки Барбары Лисков (цитата с Википедии): Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T. Роберт С. Мартин определил этот принцип так: Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом. Принцип подстановки Барбары Лисков является основной полиморфизма - одного из ключевых понятий объектно-ориентированного программирования. Относительно модификаторов доступа используется терминология "большая или меньшая видимость" ('more / less visible'). Метод или поле нельзя сделать менее видимым, то есть понизить видимость с public до protected, например. Причина в том, что это нарушит принцип подстановки. Однако, можно повышать видимость метода или поля: protected -> public package -> protected, public Вот код, который демонстрирует различные случаи изменения уровня доступа при наследовании. Его можно целиком скачать из репозитория. public class Parent { //package access void foo() { } } public class ChildPublic extends Parent { // Legal @Override public void foo() { } } public class ChildProtected extends Parent { // Legal @Override protected void foo() { } } public class ChildPrivate extends Parent { // Illegal /* @Override private void foo() { } */ } public class SamePackageAccessTest { { new Parent().foo(); //these have overriden foo() new ChildPublic().foo(); new ChildProtected().foo(); //this one had not overriden foo() new ChildPrivate().foo(); } } package otherpackage; import test.*; public class OtherPackageAccessTest { { //Legal! new ChildPublic().foo(); //illegal /* new ParentPackage().foo(); new ChildProtected().foo(); new ChildPrivate().foo(); */ } } Это перевод собственного ответа на EN.SO: What means "methods without access control can be declared more private in subclasses" in Java?
37. Что такое сериализация, для чего нужна, когда применяется? Ключевое слово transient, для чего нужно.
Сериализация (Serialization) - процесс преобразования структуры данных в линейную последовательность байтов для дальнейшей передачи или сохранения. Сериализованные объекты можно затем восстановить (десериализовать). В Java, согласно спецификации Java Object Serialization существует два стандартных способа сериализации: стандартная сериализация, через использование интерфейса java.io.Serializable и «расширенная» сериализация - java.io.Externalizable. Сериализация позволяет в определенных пределах изменять класс. Вот наиболее важные изменения, с которыми спецификация Java Object Serialization может справляться автоматически: добавление в класс новых полей; изменение полей из статических в нестатические; изменение полей из транзитных в нетранзитные. Обратные изменения (из нестатических полей в статические и из нетранзитных в транзитные) или удаление полей требуют определенной дополнительной обработки в зависимости от того, какая степень обратной совместимости необходима. ***************** Поля класса, помеченные модификатором transient, не сериализуются. Обычно в таких полях хранится промежуточное состояние объекта, которое, к примеру, проще вычислить. Другой пример такого поля - ссылка на экземпляр объекта, который не требует сериализации или не может быть сериализован. ************ При стандартной сериализации поля, имеющие модификатор static, не сериализуются. Соответственно, после десериализации это поле значения не меняет. При использовании реализации Externalizable сериализовать и десериализовать статическое поле можно, но не рекомендуется этого делать, т.к. это может сопровождаться трудноуловимыми ошибками. Поля с модификатором final сериализуются как и обычные. За одним исключением - их невозможно десериализовать при использовании Externalizable, поскольку final поля должны быть инициализированы в конструкторе, а после этого в readExternal() изменить значение этого поля будет невозможно. Соответственно, если необходимо сериализовать объект с final полем необходимо использовать только стандартную сериализацию. ************************ Serialization и SerialVersionUID всегда остается загадкой для многих Java-разработчиков. Я часто вижу вопросы насчет того что такое SerialVersionUID, или что произойдет, если я не объявлю SerialVersionUID в моем Serializable-классе? Зачем использовать SerialVersionUID внутри Serializable класса в Java - 1Помимо запутанного и редкого использования, есть еще одна причина для этого вопроса — это предупреждение Eclipse IDE об отсутствии SerialVersionUID, например: "The Serializable class Customer does not declare a static final SerialVersionUID field of type long" ("Serializable-класс Customer не объявил статическое финальное поле SerialVersionUID типа long"). В этой статье, вы сможете не только узнать основы Java SerialVersionUID но и его влияние на процесс сериализации и де-сериализации. Когда вы объявляете класс, как Serializable путем реализации интерфейса-маркера java.io.Serializable, среда выполнения Java сохраняет экземпляр этого класса на диске с помощью механизма сериализации по умолчанию, если вы не настроили процесс использования интерфейса Externalizable. Во время сериализации, среда выполнения Java создает номер версии для класса, так что она может десереализировать его позже. В Java этот номер версии известен как SerialVersionUID. Если во время десериализации, SerialVersionUID не соответствует, то процесс завершится с исключением InvalidClassException в потоке "main" java.io.InvalidClassException, а также напечатает имя класса и соответствующий SerialVersionUID. Быстрое решение для исправления этой проблемы - скопировать SerialVersionUID и определить его как константу типа private static final long в вашем классе. В этой статье мы узнаем, о том, почему мы должны использовать SerialVersionUID в Java и как использовать инструмент serialver JDK для генерации этого ID. Если вы новичок в сериализации, вы также можете посмотреть Топ 10 вопросов о сериализации Java на интервью чтобы оценить свои знания и найти пробелы в вашем понимании для дальнейшего чтения. Подобно Concurrency (параллельности) и Multi-threading (многопоточности), Serialization (сериализация) это уже другая тема, которая заслуживает чтения несколько раз. *********** Теперь мы знаем что такое SerialVersionUID и почему важно объявлять его в Serializable-классе, самое время пересмотреть некоторые важные факты связанные с Java SerialVersionUID. SerialVersionUID используется для указании версии сериализованных данных. Когда мы не объявляем SerialVersionUID в нашем классе, среда выполнения Java делает это за нас, но этот процесс чувствителен ко многим метаданным класса включая количество полей, тип полей, модификаторы доступа полей, интерфейсов, которые реализованы в классе и пр. Вы можете найти точную информацию в документации о сериализации от Oracle. Рекомендуется объявлять SerialVersionUID как private static final long переменную во избежание механизма по умолчанию. Некоторые IDE, такие как Eclipse, также выдают предупреждения если вы забыли это, например: "The Serializable class Customer does not declare a static final SerialVersionUID field of type long" ("Serializable-класс Customer не объявил статическое финальное поле SerialVersionUID типа long"). Хотя вы и можете отключить это предупреждение следуя в Window > Preferences > Java > Compiler > Errors / Warnings > Potential Programming Problems, я предлагаю не делать этого. Только когда восстановление данных не требуется я могу быть небрежным в этом. Вот как эта ошибка выглядит в Eclipse IDE, все что вам нужно это принять первое быстрое решение. Зачем использовать SerialVersionUID внутри Serializable класса в Java - 2 Вы также можете использовать утилиту serialver из JDK для генерирования Serial Version для классов в Java. Утилита также имеет GUI, который включается при передаче параметра - show. Лучшая практика в сериализации - это явно объявить SerialVersionUID, чтобы избежать любых проблем при де-сериализации, особенно если вы работаете с клиент-серверным приложением, которое опирается на сериализованные данные, например, RMI.
65. Что такое синхронизация? Зачем она нужна? Для чего нужно ключевое слово synсhronized? Какие методы синхронизации вы знаете? Какими средствами достигается?
Синхронизация это процесс, который позволяет выполнять потоки параллельно. В Java все объекты имеют одну блокировку, благодаря которой только один поток одновременно может получить доступ к критическому коду в объекте. Такая синхронизация помогает предотвратить повреждение состояния объекта. Если поток получил блокировку, ни один другой поток не может войти в синхронизированный код, пока блокировка не будет снята. Когда поток, владеющий блокировкой, выходит из синхронизированного кода, блокировка снимается. Теперь другой поток может получить блокировку объекта и выполнить синхронизированный код. Если поток пытается получить блокировку объекта, когда другой поток владеет блокировкой, поток переходит в состояние Блокировки до тех пор, пока блокировка не снимется. ************************** volatile - этот модификатор вынуждает потоки отключить оптимизацию доступа и использовать единственный экземпляр переменной. Если переменная примитивного типа - этого будет достаточно для обеспечения потокобезопасности. Если же переменная является ссылкой на объект - синхронизировано будет исключительно значение этой ссылки. Все же данные, содержащиеся в объекте, синхронизированы не будут! synchronized - это зарезервированное слово позволяет добиваться синхронизации в помеченных им методах или блоках кода. Ключевые слова transient и native к многопоточности никакого отношения не имеют, первое используется для указания полей класса, которые не нужно сериализовать, а второе - сигнализирует о том, что метод реализован в платформо-зависимом коде. *********************** Какие существуют способы синхронизации в Java? Системная синхронизация с использованием wait()/notify(). Поток, который ждет выполнения каких-либо условий, вызывает у этого объекта метод wait(), предварительно захватив его монитор. На этом его работа приостанавливается. Другой поток может вызвать на этом же самом объекте метод notify() (опять же, предварительно захватив монитор объекта), в результате чего, ждущий на объекте поток «просыпается» и продолжает свое выполнение. В обоих случаях монитор надо захватывать в явном виде, через synchronized-блок, потому как методы wait()/notify() не синхронизированы! Системная синхронизация с использованием join(). Метод join(), вызванный у экземпляра класса Thread, позволяет текущему потоку остановиться до того момента, как поток, связаный с этим экземпляром, закончит работу. Использование классов из пакета java.util.concurrent, который предоставляет набор классов для организации межпоточного взаимодействия. Примеры таких классов - Lock, Semaphore и пр.. Концепция данного подхода заключается в использовании атомарных операций и переменных. ************************* Синхронизация относится к многопоточности. Синхронизированый блок кода может быть выполнен только одним потоком одновременно. Java поддерживает несколько потоков для выполнения. Это может привести к тому, что два или более потока получат доступ к одному и тому же полю или объекту. Синхронизация это процесс, который позволяет выполнять все параллельные потоки в программе синхронно. Синхронизация позволяет избежать ошибок согласованности памяти, вызванные из-за непоследовательного доступа к общей памяти. Когда метод объявлен как синхронизированный — нить держит монитор для объекта, метод которого исполняется. Если другой поток выполняет синхронизированный метод, ваш поток заблокируется до тех пор, пока другой поток не отпустит монитор. Синхронизация достигается в Java использованием зарезервированного слова synchronized. Вы можете использовать его в своих классах определяя синхронизированные методы или блоки. Вы не сможете использовать synchronized в переменных или атрибутах в определении класса. Блокировка на уровне объекта Это механизм синхронизации не статического метода или не статического блока кода, такой, что только один поток сможет выполнить данный блок или метод на данном экземпляре класса. Это нужно делать всегда, когда необходимо сделать данные на уровне экземпляра потокобезопасными. Пример: public class DemoClass{ public synchronized void demoMethod(){} } или public class DemoClass{ public void demoMethod(){ synchronized (this) { //other thread safe code } } } или public class DemoClass{ private final Object lock = new Object(); public void demoMethod(){ synchronized (lock) { //other thread safe code } } } Блокировка на уровне класса Предотвращает возможность нескольким потокам войти в синхронизированный блок во время выполнения в любом из доступных экземпляров класса. Это означает, что если во время выполнения программы имеется 100 экземпляров класса DemoClass, то только один поток в это время сможет выполнить demoMethod() в любом из случаев, и все другие случаи будут заблокированы для других потоков. Это необходимо когда требуется сделать статические данные потокобезопасными. public class DemoClass{ public synchronized static void demoMethod(){} } или public class DemoClass{ public void demoMethod(){ synchronized (DemoClass.class) { //other thread safe code } } } или public class DemoClass { private final static Object lock = new Object(); public void demoMethod(){ synchronized (lock) { //other thread safe code } } } Некоторые важные замечания Синхронизация в Java гарантирует, что никакие два потока не смогут выполнить синхронизированный метод одновременно или параллельно. synchronized можно использовать только с методами и блоками кода. Эти методы или блоки могут быть статическими или не-статическими. когда какой либо поток входит в синхронизированный метод или блок он приобретает блокировку и всякий раз, когда поток выходит из синхронизированного метода или блока JVM снимает блокировку. Блокировка снимается, даже если нить оставляет синхронизированный метод после завершения из-за каких-либо ошибок или исключений. synchronized в Java рентерабельна это означает, что если синхронизированный метод вызывает другой синхронизированный метод, который требует такой же замок, то текущий поток, который держит замок может войти в этот метод не приобретая замок. Синхронизация в Java будет бросать NullPointerException если объект используемый в синхронизированном блоке null. Например, в вышеприведенном примере кода, если замок инициализируется как null, синхронизированный (lock) бросит NullPointerException. Синхронизированные методы в Java вносят дополнительные затраты на производительность вашего приложения. Так что используйте синхронизацию, когда она абсолютно необходима. Кроме того, рассмотрите вопрос об использовании синхронизированных блоков кода для синхронизации только критических секций кода. Вполне возможно, что и статический и не статический синхронизированные методы могут работать одновременно или параллельно, потому что они захватывают замок на другой объект. В соответствии со спецификацией языка вы не можете использовать synchronized в конструкторе это приведет к ошибке компиляции. Не синхронизируйте по не финальному (no final) полю, потому что ссылка, на не финальное поле может измениться в любое время, а затем другой поток может получить синхронизацию на разных объектах и уже не будет никакой синхронизации вообще. Лучше всего использовать класс String, который уже неизменяемый и финальный.
33. Внутренние классы, какие бывают и для каких целей используются. Области видимости данных при определенных ситуациях.
Терминология: Существует четыре категории вложенных классов: Статические вложенные классы и не статические вложенные классы. Вложенные классы, объявленные статически, называются вложенными статическими классами. Внутренние классы — когда объект внутреннего класса связан с объектом обрамляющего класса. Не статические вложенные классы называются внутренними классами, если они связанны с внешним классом. Локальные классы — объявленные внутри блока кода и не являющиеся членом обрамляющего класса. В этом случае можно рассматривать класс как локальную переменную типа класс. Анонимные классы - наследуемые, от какого либо класса, классы в которых при объявлении не задано имя класса. *************************** Вложенные и внутренние классы В литературе по Java встречаются такие термины, как "внутренние классы" inner classes и "вложенные классы" nested classes. Для вложенных классов inner классы являются подмножеством. Тем не менее часто под внутренними классами подразумеваются все вложенные - вот такой вот парадокс. Определение класса может размещаться внутри определения другого класса. Такие классы принято называть вложенными или внутренними. Область видимости вложенного класса ограничена областью видимости внешнего класса. Поэтому, если создается класс B внутри класса A, то класс B не может существовать независимо от класса A. Вложенные классы позволяют группировать классы, логически принадлежащие друг другу, и управлять доступом к ним. Существуют два типа вложенных класса - статические (static-nested-class) и нестатические (non-static). Собственно нестатические вложенные классы имеют и другое название - внутренние классы. Внешний класс иногда называют еще обрамляющим классом. Вложенные классы, nested classes Если связь между объектом внутреннего класса и объектом внешнего класса не нужна, можно сделать внутренний класс статическим static. Такой класс называют вложенным nested. Применение статического внутреннего класса означает следующее : для создания объекта статического внутреннего класса не нужен объект внешнего класса; из объекта вложенного класса нельзя обращаться к нестатическим членам внешнего класса. Вложенный класс имеет доступ к членам своего внешнего класса, в том числе и к закрытым членам. Однако, внешний класс не имеет доступа к членам вложенного класса. Вложенный класс при этом является членом внешнего класса. Статический класс объявляется ключевым словом static. При этом класс должен обращаться к нестатическим членам своего внешнего класса при помощи объекта, т.е. он не может обращаться напрямую на нестатические члены своего внешнего класса. На практие подобные классы используются редко. Внутренние классы, inner classes Нестатические вложенные классы называют также внутренними классами inner class. Внутренний класс имеет доступ ко всем переменным и методам своего внешнего класса и может непосредственно ссылаться на них. Внутренние классы создаются внутри окружающего класса : // внешний класс class Outer { int x1 = 0; int x2 = 0; void summa(final int x1, final int x2) { this.x1 = x1; this.x2 = x2; Inner inner = new Inner(); inner.display(); } // внутренний класс class Inner { void display() { System.out.println ("summa = " + String.valueOf(x1 + x2)); } } } ... class MainActivity { Outer outer = new Outer(); outer.summa(12, 11); } Внутренний класс Inner определён в области видимости класса Outer. Поэтому любой код в классе Inner может непосредственно обращаться к переменным x1, x2 внешнего класса. После создания экземпляра класса Outer вызывается его метод summa () с параметрами, который создаёт экземпляр класса Inner с вызовом метода display (). Внутренний класс можно определить не только на уровне класса, но и внутри метода или внутри тела цикла. Если понадобится создать объект внутреннего класса не в статическом методе внешнего класса, тип этого объекта должен задаваться в формате ИмяВнешнегоКласса.ИмяВнутреннегоКласса. Объект внутреннего класса связан с внешним объектом-создателем и может обращаться к его членам без каких-либо дополнительных описаний. Для внутренних классов доступны все элементы внешнего класса. Если необходимо получить ссылку на объект внешнего класса, то следует использовать наименование внешнего класса с ключевым словом this, разделенных точкой, например : Outer.this. Статические внутренние классы Статические внутренние классы декларируются внутри основного класса и обозначаются ключевым словом static. Они не имеют доступа к членам внешнего класса за исключением статических. Статический внутренний класс может содержать статические поля, методы и классы, в отличие от других типов внутренних классов. Пример : class OuterClass { private int outerField; static int staticOuterField; public OuterClass() {} static class InnerClass { int getOuterField() { return OuterClass.this.outerField; // Эта линия кода вызывает ошибку при компиляции } int getStaticOuterField() { return OuterClass.staticOuterField; // Эта линия кода синтаксически корректна } } } Локальные классы Локальные классы объявляются внутри методов основного класса и могут быть использованы только внутри этих методов. Они имеют доступ к членам внешнего класса, а также как к локальным переменным, так и к параметрам метода при одном условии - переменные и параметры используемые локальным классом должны быть задекларированы final. Локальные классы не могут содержать определение (но могут наследовать) статических полей, методов и классов (кроме констант). Пример : class OuterClass { public OuterClass(){} private int outerField; InnerClass inner; // Эта линия кода вызывает ошибку при компиляции void methodWithLocalClass (final int parameter) { InnerClass innerInsideMehod; // Эта линия кода синтаксически корректна int notFinal = 0; class InnerClass { int getOuterField() { return OuterClass.this.outerField; // Эта линия кода синтаксически корректна } notFinal++; // Эта линия кода вызывает ошибку при компиляции int getParameter() { return parameter; // Эта линия кода синтаксически корректна } }; } }; Локальный класс можно объявить внутри любого блока операторов. Например, внутри методов, циклов или операторов if. В следующем примере, проверяющем правильность телефонных номеров, объявляется локальный класс PhoneNumber в методе validatePhoneNumber : public class LocalClassExample { static String regularExpression = "[^0-9]"; public static void validatePhoneNumber (String phoneNumber1, String phoneNumber2) { final int numberLength = 10; class PhoneNumber { String formattedPhoneNumber = null; PhoneNumber(String phoneNumber){ // numberLength = 7; String currentNumber = phoneNumber.replaceAll(regularExpression, ""); if (currentNumber.length() == numberLength) formattedPhoneNumber = currentNumber; else formattedPhoneNumber = null; } public String getNumber() { return formattedPhoneNumber; } } PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1); PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2); if (myNumber1.getNumber() == null) System.out.println("First number is invalid"); else System.out.println("First number is " + myNumber1.getNumber()); if (myNumber2.getNumber() == null) System.out.println("Second number is invalid"); else System.out.println("Second number is " + myNumber2.getNumber()); } public static void main(String... args) { validatePhoneNumber("123-456-7890", "456-7890"); } } Данная программа проверяет телефонные номера на соответствие некоторому стандарту. Сперва из номера удаляются все символы, отличные от цифр (0-9). После, происходит проверка длины номера и, если номер состоит из 10 цифр, то номер корректный. Данный пример выведет : First number is 1234567890 Second number is invalid У локальных классов есть несколько ограничений : они видны только в пределах блока, в котором объявлены; они не могут быть объявлены как private, public, protected или static; они не могут иметь внутри себя статических объявлений (полей, методов, классов); исключением являются константы (static final); ************* Причины использования вложенных классов такие. Если класс полезен только для одного другого класса, то вполне логично встроить его в этот класс и хранить их вместе. Использование вложенных классов увеличивает инкапсуляцию. Рассмотрим два класса верхнего уровня, A и B, где B нужен доступ к членам, которые иначе были бы объявлены закрытыми. Скрывая класс «B» в пределах класса «А», члены класса «А» могут быть объявлены закрытыми, и «B» может получить доступ к ним. Кроме того, сам «B» может быть скрыт от внешнего мира. ********************** Статические Вложенные Классы Static Nested Classes ********************** Причины использования статических вложенных классов такие. Для случая, когда связь между объектом вложенного класса и объектом внешнего класса не нужна, можно сделать вложенный класс статическим(static). Так как внутренний класс связан с экземпляром, он не может определить в себе любые статические члены. Статические вложенные классы не имеют ограничений по объявлению своих данных и полей как static. Из вложенного статического класса мы не имеем доступа к внешней не статической переменной внешнего класса. Вывод: Мы не имеем доступа к не статическому полю внешнего класса, через статический контекст вложенного класса. Это подобно тому, как мы не имеем доступа из статического метода к нестатическим переменным класса. Точно также из статического вложенного класса мы не имеем доступа к нестатическим переменным внешнего класса. Но мы имеем доступ к приватным статическим полям внешнего класса из вложенного статичного класса. ********************** Рассмотрим свойства внутренних классов. Внутренние классы есть смысл использовать, если они будут использовать элементы родителя, чтобы не передавать лишнего в конструкторах. Внутренний класс неявно наследуется от внешнего класса, хотя мы не используем ключевое слово extends в случае с классом или implements в случае с интерфейсом. То есть, во внутреннем классе мы можем использовать весь унаследованный функционал внешнего класса. Может показаться, что это сомнительно. Но это дает нам более гибкий подход. Таким образом мы можем использовать во внутреннем классе, функционал унаследованный от внешнего класса. Внутренний класс стоит использовать, когда нам нужна инкапсуляция. Во внутреннем классе мы, таким образом закрываем всё от «внешнего мира».
31. Mutable и Immutable классы. Привести примеры. Как создать класс, который будет immutable.
Только так все эти методы String и работают. С объектом "I love Java" ничего сделать нельзя. Только создать новый объект, и написать: "Новый объект = результат каких-то манипуляций с объектом "I love Java"". Какие типы еще относятся к Immutable? Из того, что тебе железобетонно нужно запомнить уже сейчас — все классы-обертки над примитивными типами — неизменяемые. Integer, Byte, Character, Short, Boolean, Long, Double, Float — все эти классы создают Immutable объекты. Сюда же относятся и классы, используемые для создания больших чисел — BigInteger и BigDecimal. Мы недавно проходили исключения и затрагивали StackTrace. Так вот: объекты класса java.lang.StackTraceElement тоже неизменяемые. Это логично: если бы кто-то мог изменять данные нашего стэка, это могло бы свести на нет всю работу с ним. Представь, что кто-нибудь заходит в StackTrace и меняет OutOfMemoryError на FileNotFoundException. А тебе с этим стеком работать и искать причину ошибки. А программа при этом вообще не использует файлы :) Поэтому от греха подальше эти объекты сделали неизменяемыми. Ну, со StackTraceElement более-менее понятно. А зачем кому-то понадобилось делать неизменяемыми строки? В чем проблема, если бы можно было менять их значения. Наверное, даже удобнее бы было :/ Причин тут несколько. Во-первых, экономия памяти. Неизменяемые строки можно помещать в String Pool и использовать каждый раз одну и ту же вместо создания новых. Во-вторых, безопасность. Например, большинство логинов и паролей в любой программе — строки. Возможность их изменения могла бы повлечь проблемы с авторизацией. Есть и другие причины, но пока что мы не дошли к ним в изучении Java — вернемся попозже. У Mutable objects есть поля, которые можно изменить, неизменяемые объекты не имеют полей, которые могут быть изменены после создания объекта. 18 Для начала, есть разница между immutable-объектом (то есть, неизменяемым), и final-ссылкой. Ключевое слово final для объектных типов гарантирует неизменяемость лишь ссылки, но не самого объекта. Например, если у вас есть final-ссылка на ArrayList<T>, вы тем не менее можете добавлять в него новые элементы или изменять существующие. В случае же immutable-объекта объект после окончания конструктора не изменяется вообще. Одного лишь модификатора final для этого недостаточно, необходимо, чтобы все подбъекты были тоже неизменяемыми. Вы в принципе можете держать внутри ссылку на изменяемый объект, но обращаться с ним так, чтобы он не менялся. Использование неизменяемых объектов даёт много выгод. Например, о таком объекте намного легче судить в ситуации, когда во многих частях программы есть ссылка на него (для изменяемого объекта, любая часть программы может вызвать мутирующую функцию в практически любой момент времени и из любого потока). Но то, что для нас важно в контексте вопроса — неизменяемые объекты не требуют синхронизации при многопоточном доступе. Вот собственно и вся рекомендация: используйте неизменяемые объекты, и вам не придётся думать о том, что нужно, а что не нужно синхронизировать. Единственная возможная проблема — если вы внутри ещё не отработавшего конструктора публикуете ссылку на объект, через которую к нему может получить доступ кто-нибудь ещё, и увидеть объект в изменяющемся состоянии! (Это бывает не так уж и редко. Например, иногда программист хочет добавить объект в конструкторе в коллекцию всех объектов данного типа.) Следует различать действительно неизменяемые объекты, и объекты, имеющие лишь интерфейс «только для чтения». При чтении объект тем не менее может менять свою внутреннюю структуру (например, кэшировать самый свежий запрос данных). Такие объекты не являются в строгом смысле неизменяемыми, и не могут быть использованы из разных потоков без предосторожностей. (Поэтому, если ваш объект включает другие объекты, убедитесь, что документация гарантирует их неизменяемость!) Обратите внимание, что для полей неизменяемого объекта вы практически обязаны использовать final! Дело в так называемой безопасной публикации. Смотрите. Инструкции в Java-программе могут быть переставлены как оптимизатором, так и процессором (у Java достаточно слабая модель памяти). Поэтому, если не предпринимать специальных действий, окончание работы конструктора и присвоение значений полям может быть переставлено (но невидимо в рамках текущего потока)! Использование final гарантирует, что такого не произойдёт. Immutable объект - это объект, состояние которого после создания невозможно изменить. В случае Java это значит что все поля экземпляра у класс отмечены как final и являются примитивами или тоже immutable типами. Пример: public class ImmutablePoint { private final int x; private final int y; private final String description; public ImmutablePoint(int x, int y, String description) { this.x = x; this.y = y; this.description = description; } } После создания экземпляра ImmutablePoint его модификация невозможна. Простейший пример immutable класса из JDK это String. Любые методы, которые вы вызовите на строке (например description.toLowerCase()) вернут новую строку, а не модифицируют исходную. Пример mutable класс из JDK - Date. Например myDate.setHours(x) модифицирует сам экземпляр myDate! В случае многопоточного программирования преимущества immutable классов очевидны: после создания объекты можно передавать другим потокам и они всегда будут в актуальном состоянии. Т.е. вам не надо проверять не устарело ли состояние вашего экземпляра и не модифицировал ли его другой поток пока вы с ним работаете. Например, у вас есть метод bill(Date endDate), в нём вы наивно проверяете соответствие endDate каким-то предварительным условиям и начинаете с ней работать. В этот момент другой поток может изменить endDate, например установит её глубоко в прошлое. Последствия могут быть самыми удивительными. *********** Ниже приведены жесткие требования неизменяемого объекта. Сделать финал класса сделать все члены окончательными, установить их явно, в статическом блоке или в конструкторе Сделайте всех участников приватными Нет методов, которые изменяют состояние Будьте предельно осторожны, чтобы ограничить доступ к изменяемым элементам (помните, что поле может быть final но объект все еще может быть изменяемым. private final Date imStillMutable). Вы должны сделать defensive copies в этих случаях. Причины для того, чтобы сделать final класса очень тонкими и часто упускаются из виду. Если не окончательные пользователи могут свободно расширять ваш класс, переопределять public или protected поведение, добавлять изменяемые свойства, а затем предоставлять свой подкласс в качестве замены. Объявляя final класса, вы можете быть уверены, что этого не произойдет. На практике очень часто встречается вышеуказанная проблема в средах внедрения зависимостей. Вы не создаете явные экземпляры, и ссылка на суперкласс, которую вы даете, может фактически быть подклассом. Вывод заключается в том, что для того, чтобы получить твердые гарантии неизменности, вы должны отметить класс как final. Это подробно рассматривается в Joshua Bloch Effective Java и явно упоминается в спецификации модели памяти Java. public class MyApp{ /** * @param args */ public static void main(String[] args){ System.out.println("Hello World!"); OhNoMutable mutable = new OhNoMutable(1, 2); ImSoImmutable immutable = mutable; /* * Ahhhh Prints out 3 just like I always wanted * and I can rely on this super immutable class * never changing. So its thread safe and perfect */ System.out.println(immutable.add()); /* Some sneak programmer changes a mutable field on the subclass */ mutable.field3=4; /* * Ahhh let me just print my immutable * reference again because I can trust it * so much. * */ System.out.println(immutable.add()); /* Why is this buggy piece of crap printing 7 and not 3 It couldn't have changed its IMMUTABLE!!!! */ } } /* This class adheres to all the principles of * good immutable classes. All the members are private final * the add() method doesn't modify any state. This class is * just a thing of beauty. Its only missing one thing * I didn't declare the class final. Let the chaos ensue */ public class ImSoImmutable{ private final int field1; private final int field2; public ImSoImmutable(int field1, int field2){ this.field1 = field1; this.field2 = field2; } public int add(){ return field1+field2; } } /* This class is the problem. The problem is the overridden method add(). Because it uses a mutable member it means that I can't guarantee that all instances of ImSoImmutable are actually immutable. */ public class OhNoMutable extends ImSoImmutable{ public int field3 = 0; public OhNoMutable(int field1, int field2){ super(field1, field2); } public int add(){ return super.add()+field3; } }
40. Что такое ClassLoader? Если изменить static переменную в классе, загруженном одним ClassLoader, что будет видно в том же классе, загруженном другим. Возможно ли синглтоны создавать несколько раз?
Традиционно Singleton создает свой собственный экземпляр, и он создает его только один раз. В этом случае невозможно создать второй экземпляр. Если вы используете Dependency Injection, вы можете позволить фреймворку создать синглтон для вас. Синглтон не защищает от других экземпляров (т.е. Имеет открытый конструктор), но инфраструктура инъекции зависимостей создает экземпляр только одного экземпляра. В этом случае вы можете создать больше экземпляров для тестирования, и ваш объект не будет загроможден одиночным кодом. В системе всегда существует Загрузка байт-кода Загрузка Class Class
59. Как работают методы wait и notify/notifyAll?
Эти методы определены у класса Object и предназначены для взаимодействия потоков между собой при межпоточной синхронизации. wait(): освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод notify()/notifyAll(); notify(): продолжает работу потока, у которого ранее был вызван метод wait(); notifyAll(): возобновляет работу всех потоков, у которых ранее был вызван метод wait(). Когда вызван метод wait(), поток освобождает блокировку на объекте и переходит из состояния Работающий (Running) в состояние Ожидания (Waiting). Метод notify() подаёт сигнал одному из потоков, ожидающих на объекте, чтобы перейти в состояние Работоспособный (Runnable). При этом невозможно определить, какой из ожидающих потоков должен стать работоспособным. Метод notifyAll() заставляет все ожидающие потоки для объекта вернуться в состояние Работоспособный (Runnable). Если ни один поток не находится в ожидании на методе wait(), то при вызове notify() или notifyAll() ничего не происходит. Поток может вызвать методы wait() или notify() для определённого объекта, только если он в данный момент имеет блокировку на этот объект. wait(), notify() и notifyAll() должны вызываться только из синхронизированного кода.
может ли объект получить доступ к private-переменной класса. Каким образом?
внутри класса доступ к приватной переменной открыт без ограничений(инфа: модификаторы доступа). доступ вне класса осуществляется через открытые(паблик) методы : геттеры и сеттеры (от анг. get и set). если геттеры и сеттеры отсутствуют - рефлексия(крайне не рекомендуется). Статический вложенный класс имеет полный доступ ко всем членам содержащего его класса, в том числе к членам, объявленным как privat.
68. По каким объектам синхронизируются статические и нестатические методы?
статические методы синхронизируются по классу нестатические методы по объекту