ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 01.12.2023
Просмотров: 476
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
«возрождением»
объекта и считается антипаттерном. Главная проблема такого трюка в том, что
«возродить» объект можно только 1 раз.
Что произойдет со сборщиком мусора, если выполнение метода finalize()
требует ощутимо много времени или в процессе выполнения будет
выброшено исключение?
Непосредственно вызов finalize() происходит в отдельном потоке Finalizer
(java.lang.ref.Finalizer.FinalizerThread), который создается при запуске виртуальной машины
(в статической секции при загрузке класса Finalizer). Методы finalize() вызываются последовательно в том порядке, в котором были добавлены в список сборщиком мусора.
Соответственно, если какой-то finalize() зависнет, он подвесит поток Finalizer, но не сборщик мусора. Это в частности означает, что объекты, не имеющие метода finalize(), будут исправно удаляться, а вот имеющие будут добавляться в очередь, пока поток Finalizer не освободится, не завершится приложение или не кончится память.
То же самое применимо и к выброшенным в процессе finalize() исключениям: метод runFinalizer() у потока Finalizer игнорирует все исключения, выброшенные в момент выполнения finalize(). Таким образом, возникновение исключительной ситуации никак не скажется на работоспособности сборщика мусора.
Чем отличаются final, finally и finalize()?
Модификатор final:
•
класс не может иметь наследников;
•
метод не может быть переопределен в классах наследниках;
•
поле не может изменить свое значение после инициализации;
•
локальные переменные не могут быть изменены после присвоения им значения;
•
параметры методов не могут изменять свое значение внутри метода.
Оператор finally гарантирует, что определенный в нем участок кода будет выполнен независимо от того, какие исключения были возбуждены и перехвачены в блоке try-catch.
Метод finalize() вызывается перед тем, как сборщик мусора будет проводить удаление объекта.
Что такое Heap- и Stack-память в Java? Какая разница между ними?
Heap (куча) используется Java Runtime для выделения памяти под объекты и классы.
Создание нового объекта также происходит в куче. Она же является областью работы сборщика мусора. Любой объект, созданный в куче, имеет глобальный доступ и на него могут ссылаться из любой части приложения.
Stack (стек) – это область хранения данных также находится в общей оперативной памяти
(RAM). Всякий раз, когда вызывается метод, в памяти стека создается новый блок, который содержит примитивы и ссылки на другие объекты в методе. Как только метод заканчивает работу, блок перестает использоваться, тем самым предоставляя доступ для следующего метода. Размер стековой памяти намного меньше объема памяти в куче. Стек в Java работает по схеме LIFO (последний-зашел-первый-вышел).
Различия между Heap и Stack памятью:
куча используется всеми частями приложения, в то время как стек используется только одним потоком исполнения программы;
всякий раз, когда создается объект, он всегда хранится в куче, а в памяти стека содержится лишь ссылка на него, память стека содержит только локальные переменные примитивных типов и ссылки на объекты в куче;
объекты в куче доступны из любой точки программы, в то время как стековая память не может быть доступна для других потоков;
стековая память существует лишь какое-то время работы программы, а память в куче живет с самого начала до конца работы программы;
если память стека полностью занята, то Java Runtime бросает исключение
java.lang.StackOverflowError, если заполнена память кучи, то бросается исключение
java.lang.OutOfMemoryError: Java Heap Space;
размер памяти стека намного меньше памяти в куче;
из-за простоты распределения памяти стековая память работает намного быстрее кучи.
Для определения начального и максимального размера памяти в куче используются опции
JVM -Xms и -Xmx. Для стека определить размер памяти можно с помощью опции -Xss.
Верно ли утверждение, что примитивные типы данных всегда хранятся в
стеке, а экземпляры ссылочных типов данных – в куче?
Не совсем. Примитивное поле экземпляра класса хранится не в стеке, а в куче. Любой объект (все, что явно или неявно создается при помощи оператора new) хранится в куче.
Ключевые слова
abstract
, assert, break, case, catch, class, const*, continue, default, do, else, enum, extends, final,
finally, for, goto*, if, implements, import, instanceof, interface, native, new, package, return, static,
strictfp, super, switch, synchronized, this, throw, throws, transi ent, try, void, volatile, while.
* – зарезервированное слово, не используется.
Для чего используется оператор assert?
Assert (утверждение) – это специальная конструкция, позволяющая проверять предположения о значениях произвольных данных в произвольном месте программы.
Утверждение может автоматически сигнализировать об обнаружении некорректных данных,
что обычно приводит к аварийному завершению программы с указанием места обнаружения некорректных данных.
Утверждения существенно упрощают локализацию ошибок в коде. Даже проверка результатов выполнения очевидного кода может оказаться полезной при последующем рефакторинге, после которого код может стать не настолько очевидным и в него может закрасться ошибка.
Обычно утверждения оставляют включенными во время разработки и тестирования программ, но отключают в релиз-версиях программ.
Т. к. утверждения могут быть удалены на этапе компиляции либо во время исполнения программы, они не должны менять поведение программы. Если в результате удаления утверждения поведение программы может измениться, то это явный признак неправильного использования assert. Таким образом, внутри assert нельзя вызывать методы, изменяющие состояние программы, либо внешнего окружения программы.
В Java проверка утверждений реализована с помощью оператора assert, который имеет форму:
assert [Выражение типа boolean]; или assert [Выражение типа boolean] : [Выражение
любого типа, кроме void];
Во время выполнения программы в том случае, если проверка утверждений включена,
вычисляется значение булевского выражения, и если его результат false, то генерируется исключение java.lang.AssertionError. В случае использования второй формы оператора assert выражение после двоеточия задает детальное сообщение о произошедшей ошибке
(вычисленное выражение будет преобразовано в строку и передано конструктору
AssertionError).
Какие примитивные типы данных есть в Java?
Числа инициализируются 0 или 0.0;
char – \u0000;
boolean – false;
Объекты (в том числе String) – null.
Что такое char?
16-разрядное беззнаковое целое, представляющее собой символ UTF-16 (буквы и цифры).
Сколько памяти занимает boolean?
Зависит от реализации JVM: минимум 1 байт в массивах, 4 байта в коде.
Логические операторы
&: Логическое AND (И);
&&: Сокращенное AND;
|: Логическое OR (ИЛИ);
||: Сокращенное OR;
^: Логическое XOR (исключающее OR (ИЛИ));
!: Логическое унарное NOT (НЕ);
&=: AND с присваиванием;
|=: OR с присваиванием;
^=: XOR с присваиванием;
==: Равно;
!=: Не равно;
?:: Тернарный (троичный) условный оператор.
Тернарный условный оператор
Оператор, которым можно заменить некоторые конструкции операторов if-then-else.
Выражение записывается в следующей форме:
условие ? выражение1 : выражение2
Если условие выполняется, то вычисляется выражение1 и его результат становится результатом выполнения всего оператора. Если же условие равно false, то вычисляется
выражение2 и его значение становится результатом работы оператора. Оба операнда выражение1 и выражение2 должны возвращать значение одинакового (или совместимого)
типа.
Какие побитовые операции вы знаете?
: Побитовый унарный оператор NOT;
&: Побитовый AND;
&=: Побитовый AND с присваиванием;
|: Побитовый OR;
|=: Побитовый OR с присваиванием;
^: Побитовый исключающее XOR;
^=: Побитовый исключающее XOR с присваиванием;
>>: Сдвиг вправо (деление на 2 в степени сдвига);
>>=: Сдвиг вправо с присваиванием;
>>>: Сдвиг вправо без учета знака;
>>>=: Сдвиг вправо без учета знака с присваиванием;
<<: Сдвиг влево (умножение на 2 в степени сдвига);
<<=: Сдвиг влево с присваиванием.
Что такое классы-обертки?
Обертка – это специальный класс, который хранит внутри себя значение примитива. Нужны для реализации дженериков (или коллекций), объектов.
Что такое автоупаковка и автораспаковка?
Автоупаковка – присвоение классу обертки значения примитивного типа.
Автораспаковка – присвоение переменной примитивного типа значение класса обертки.
Необходимы для присваивания ссылок-примитивов объектам их классов-оберток (и наоборот). Не требуется ничего делать, все происходит автоматически.
Автоупаковка – это механизм неявной инициализации объектов классов-оберток (Byte, Short,
Integer, Long, Float, Double, Character, Boolean) значениями соответствующих им исходных примитивных типов (byte, short, int...), без явного использования конструктора класса.
Автоупаковка происходит при прямом присваивании примитива классу-обертке (с помощью оператора =), либо при передаче примитива в параметры метода (типа класса-обертки).
Автоупаковке в классы-обертки могут быть подвергнуты как переменные примитивных типов,
так и константы времени компиляции (литералы и final-примитивы). При этом литералы должны быть синтаксически корректными для инициализации переменной исходного примитивного типа.
Автоупаковка переменных примитивных типов требует точного соответствия типа исходного примитива типу класса-обертки. Например, попытка упаковать переменную типа byte в Short без предварительного явного приведения byte в short вызовет ошибку компиляции.
Автоупаковка констант примитивных типов допускает более широкие границы соответствия.
В этом случае компилятор способен предварительно осуществлять неявное расширение/сужение типа примитивов:
•
неявное расширение/сужение исходного типа примитива до типа примитива соответствующего классу-обертке (для преобразования int в Byte сначала компилятор самостоятельно неявно сужает int к byte);
•
автоупаковку примитива в соответствующий класс-обертку. Однако, в этом случае существуют два дополнительных ограничения: a) присвоение примитива обертке может производится только оператором = (нельзя передать такой примитив в параметры метода без явного приведения типов) b) тип левого операнда не должен быть старше чем Character, тип правого не должен старше, чем int:
допустимо расширение/сужение byte в/из short, byte в/из char, short в/из char и только сужение byte из int, short из int, char из int. Все остальные варианты требуют явного приведения типов).
Дополнительной особенностью целочисленных классов-оберток, созданных автоупаковкой констант в диапазоне -128 ... +127, является то, что они кешируются JVM. Поэтому такие обертки с одинаковыми значениями будут являться ссылками на один объект.
Что такое явное и неявное приведение типов? В каких случаях в java нужно
использовать явное приведение?
Java является строго типизированным языком программирования, а это означает, что каждое выражение и каждая переменная имеет строго определенный тип уже на момент компиляции. Однако определен механизм приведения типов (casting) – способ преобразования значения переменной одного типа в значение другого типа.
В Java существуют несколько разновидностей приведения:
Тождественное (identity). Преобразование выражения любого типа к точно такому же типу всегда допустимо и происходит автоматически.
Расширение (повышение, upcasting) примитивного типа (widening primitive). Означает, что осуществляется переход от менее емкого типа к более емкому. Например, от типа byte
(длина 1 байт) к типу int (длина 4 байта). Такие преобразование безопасны в том смысле, что новый тип всегда гарантировано вмещает в себя все данные, которые хранились в старом типе, и таким образом не происходит потери данных. Этот тип приведения всегда допустим и происходит автоматически.
Сужение (понижение, downcasting) примитивного типа (narrowing primitive). Означает, что переход осуществляется от более емкого типа к менее емкому. При таком преобразовании есть риск потерять данные. Например, если число типа int было больше 127, то при приведении его к byte значения битов старше восьмого будут потеряны. В Java такое преобразование должно совершаться явным образом, при этом все старшие биты, не умещающиеся в новом типе, просто отбрасываются – никакого округления или других действий для получения более корректного результата не производится.
Расширение объектного типа (widening reference). Означает неявное восходящее приведение типов или переход от более конкретного типа к менее конкретному, т. е. переход от потомка к предку. Разрешено всегда и происходит автоматически.
Сужение объектного типа (narrowing reference). Означает нисходящее приведение, то есть приведение от предка к потомку (подтипу). Возможно только если исходная переменная является подтипом приводимого типа. При несоответствии типов в момент выполнения выбрасывается исключение ClassCastException. Требует явного указания типа.
Преобразование к строке (to String). Любой тип может быть приведен к строке, т. е. к экземпляру класса String.
Запрещенные преобразования (forbidden). Не все приведения между произвольными типами допустимы. Например, к запрещенным преобразованиям относятся приведения от
любого ссылочного типа к примитивному и наоборот (кроме преобразования к строке). Кроме того, невозможно привести друг к другу классы, находящиеся на разных ветвях дерева наследования и т. п.
При приведении ссылочных типов с самим объектом ничего не происходит, меняется лишь тип ссылки, через которую происходит обращение к объекту.
Для проверки возможности приведения нужно воспользоваться оператором instanceof:
Parent parent = new Child();
if (parent instanceof Child) {
Child child = (Child) parent;
}
Когда в приложении может быть выброшено исключение
ClassCastException?
ClassCastException (потомок RuntimeException) – исключение, которое будет выброшено при ошибке приведения типа.
Что такое пул интов?
В классе-обертке Integer есть внутренний класс IntegerCache – пул (pool) целых чисел в промежутке [-128; 127], так как это самый часто встречающийся диапазон. Он объявлен как private static. В этом внутреннем классе кешированные объекты находятся в массиве cache[].
Кеширование выполняется при первом использовании класса-обертки. После первого использования вместо создания нового экземпляра (кроме использования конструктора),
используются кешированные объекты, JVM берет их из пула.
Можно ли изменить размер пула int?
Не из кода, а в параметре JVM.
Какие еще есть пулы примитивов?
У всех целочисленных и char, но размеры изменять нельзя, можно только у int.
Какие есть особенности класса String?
•
это неизменяемый (immutable) и финализированный тип данных;
•
все объекты класса String JVM хранит в пуле строк;
•
объект класса String можно получить, используя двойные кавычки;
•
можно использовать оператор + для конкатенации строк;
•
начиная с Java 7, строки можно использовать в конструкции switch.
Что такое «пул строк»?
Пул строк – это набор строк, хранящийся в Heap.
•
пул строк возможен благодаря неизменяемости строк в Java и реализации идеи интернирования строк;
•
пул строк помогает экономить память, но по этой же причине создание строки занимает больше времени;
•
если для создания строки используются ", то сначала ищется строка в пуле с таким же значением, если находится, то просто возвращается ссылка, иначе создается новая строка в пуле, а затем возвращается ссылка на нее;
•
при использовании оператора new создается новый объект String, затем при помощи метода intern() эту строку можно поместить в пул или же получить из пула ссылку на другой объект String с таким же значением;
•
пул строк является примером паттерна «Приспособленец» (Flyweight).
Почему не рекомендуется изменять строки в цикле? Что рекомендуется
использовать?
Строка – неизменяемый класс, поэтому растет потребление ресурсов при редактировании,
т. к. при каждой итерации будет создаваться новый объект строки. Рекомендуется
использовать StringBuilder.
Почему char[] предпочтительнее String для хранения пароля?
С момента создания строка остается в пуле до тех пор, пока не будет удалена сборщиком мусора. Поэтому даже после окончания использования пароля он некоторое время продолжает оставаться доступным в памяти и способа избежать этого не существует. Это представляет определенный риск для безопасности, поскольку кто-либо, имеющий доступ к памяти, сможет найти пароль в виде текста. В случае использования массива символов для хранения пароля имеется возможность очистить его сразу по окончанию работы с паролем,
позволяя избежать риска безопасности, свойственного строке.
Почему String неизменяемый и финализированный класс?
Есть несколько преимуществ в неизменности строк:
•
Пул строк возможен только потому, что строка неизменяемая, таким образом
виртуальная машина сохраняет больше свободного места в Heap, поскольку разные строковые переменные указывают на одну и ту же переменную в пуле.
Если бы строка была изменяемой, то интернирование строк не было бы возможным, потому что изменение значения одной переменной отразилось бы также и на остальных переменных, ссылающихся на эту строку.
•
Если строка будет изменяемой, тогда это станет серьезной угрозой
1 2 3 4 5 6 7 8 9 ... 25
•
параметры методов не могут изменять свое значение внутри метода.
Оператор finally гарантирует, что определенный в нем участок кода будет выполнен независимо от того, какие исключения были возбуждены и перехвачены в блоке try-catch.
Метод finalize() вызывается перед тем, как сборщик мусора будет проводить удаление объекта.
Что такое Heap- и Stack-память в Java? Какая разница между ними?
Heap (куча) используется Java Runtime для выделения памяти под объекты и классы.
Создание нового объекта также происходит в куче. Она же является областью работы сборщика мусора. Любой объект, созданный в куче, имеет глобальный доступ и на него могут ссылаться из любой части приложения.
Stack (стек) – это область хранения данных также находится в общей оперативной памяти
(RAM). Всякий раз, когда вызывается метод, в памяти стека создается новый блок, который содержит примитивы и ссылки на другие объекты в методе. Как только метод заканчивает работу, блок перестает использоваться, тем самым предоставляя доступ для следующего метода. Размер стековой памяти намного меньше объема памяти в куче. Стек в Java работает по схеме LIFO (последний-зашел-первый-вышел).
Различия между Heap и Stack памятью:
куча используется всеми частями приложения, в то время как стек используется только одним потоком исполнения программы;
всякий раз, когда создается объект, он всегда хранится в куче, а в памяти стека содержится лишь ссылка на него, память стека содержит только локальные переменные примитивных типов и ссылки на объекты в куче;
объекты в куче доступны из любой точки программы, в то время как стековая память не может быть доступна для других потоков;
стековая память существует лишь какое-то время работы программы, а память в куче живет с самого начала до конца работы программы;
если память стека полностью занята, то Java Runtime бросает исключение
java.lang.StackOverflowError, если заполнена память кучи, то бросается исключение
java.lang.OutOfMemoryError: Java Heap Space;
размер памяти стека намного меньше памяти в куче;
из-за простоты распределения памяти стековая память работает намного быстрее кучи.
Для определения начального и максимального размера памяти в куче используются опции
JVM -Xms и -Xmx. Для стека определить размер памяти можно с помощью опции -Xss.
Верно ли утверждение, что примитивные типы данных всегда хранятся в
стеке, а экземпляры ссылочных типов данных – в куче?
Не совсем. Примитивное поле экземпляра класса хранится не в стеке, а в куче. Любой объект (все, что явно или неявно создается при помощи оператора new) хранится в куче.
Ключевые слова
abstract
, assert, break, case, catch, class, const*, continue, default, do, else, enum, extends, final,
finally, for, goto*, if, implements, import, instanceof, interface, native, new, package, return, static,
strictfp, super, switch, synchronized, this, throw, throws, transi ent, try, void, volatile, while.
* – зарезервированное слово, не используется.
Для чего используется оператор assert?
Assert (утверждение) – это специальная конструкция, позволяющая проверять предположения о значениях произвольных данных в произвольном месте программы.
Утверждение может автоматически сигнализировать об обнаружении некорректных данных,
что обычно приводит к аварийному завершению программы с указанием места обнаружения некорректных данных.
Утверждения существенно упрощают локализацию ошибок в коде. Даже проверка результатов выполнения очевидного кода может оказаться полезной при последующем рефакторинге, после которого код может стать не настолько очевидным и в него может закрасться ошибка.
Обычно утверждения оставляют включенными во время разработки и тестирования программ, но отключают в релиз-версиях программ.
Т. к. утверждения могут быть удалены на этапе компиляции либо во время исполнения программы, они не должны менять поведение программы. Если в результате удаления утверждения поведение программы может измениться, то это явный признак неправильного использования assert. Таким образом, внутри assert нельзя вызывать методы, изменяющие состояние программы, либо внешнего окружения программы.
В Java проверка утверждений реализована с помощью оператора assert, который имеет форму:
assert [Выражение типа boolean]; или assert [Выражение типа boolean] : [Выражение
любого типа, кроме void];
Во время выполнения программы в том случае, если проверка утверждений включена,
вычисляется значение булевского выражения, и если его результат false, то генерируется исключение java.lang.AssertionError. В случае использования второй формы оператора assert выражение после двоеточия задает детальное сообщение о произошедшей ошибке
(вычисленное выражение будет преобразовано в строку и передано конструктору
AssertionError).
Какие примитивные типы данных есть в Java?
Числа инициализируются 0 или 0.0;
char – \u0000;
boolean – false;
Объекты (в том числе String) – null.
Что такое char?
16-разрядное беззнаковое целое, представляющее собой символ UTF-16 (буквы и цифры).
Сколько памяти занимает boolean?
Зависит от реализации JVM: минимум 1 байт в массивах, 4 байта в коде.
Логические операторы
&: Логическое AND (И);
&&: Сокращенное AND;
|: Логическое OR (ИЛИ);
||: Сокращенное OR;
^: Логическое XOR (исключающее OR (ИЛИ));
!: Логическое унарное NOT (НЕ);
&=: AND с присваиванием;
|=: OR с присваиванием;
^=: XOR с присваиванием;
==: Равно;
!=: Не равно;
?:: Тернарный (троичный) условный оператор.
Тернарный условный оператор
Оператор, которым можно заменить некоторые конструкции операторов if-then-else.
Выражение записывается в следующей форме:
условие ? выражение1 : выражение2
Если условие выполняется, то вычисляется выражение1 и его результат становится результатом выполнения всего оператора. Если же условие равно false, то вычисляется
выражение2 и его значение становится результатом работы оператора. Оба операнда выражение1 и выражение2 должны возвращать значение одинакового (или совместимого)
типа.
Какие побитовые операции вы знаете?
: Побитовый унарный оператор NOT;
&: Побитовый AND;
&=: Побитовый AND с присваиванием;
|: Побитовый OR;
|=: Побитовый OR с присваиванием;
^: Побитовый исключающее XOR;
^=: Побитовый исключающее XOR с присваиванием;
>>: Сдвиг вправо (деление на 2 в степени сдвига);
>>=: Сдвиг вправо с присваиванием;
>>>: Сдвиг вправо без учета знака;
>>>=: Сдвиг вправо без учета знака с присваиванием;
<<: Сдвиг влево (умножение на 2 в степени сдвига);
<<=: Сдвиг влево с присваиванием.
Что такое классы-обертки?
Обертка – это специальный класс, который хранит внутри себя значение примитива. Нужны для реализации дженериков (или коллекций), объектов.
Что такое автоупаковка и автораспаковка?
Автоупаковка – присвоение классу обертки значения примитивного типа.
Автораспаковка – присвоение переменной примитивного типа значение класса обертки.
Необходимы для присваивания ссылок-примитивов объектам их классов-оберток (и наоборот). Не требуется ничего делать, все происходит автоматически.
Автоупаковка – это механизм неявной инициализации объектов классов-оберток (Byte, Short,
Integer, Long, Float, Double, Character, Boolean) значениями соответствующих им исходных примитивных типов (byte, short, int...), без явного использования конструктора класса.
Автоупаковка происходит при прямом присваивании примитива классу-обертке (с помощью оператора =), либо при передаче примитива в параметры метода (типа класса-обертки).
Автоупаковке в классы-обертки могут быть подвергнуты как переменные примитивных типов,
так и константы времени компиляции (литералы и final-примитивы). При этом литералы должны быть синтаксически корректными для инициализации переменной исходного примитивного типа.
Автоупаковка переменных примитивных типов требует точного соответствия типа исходного примитива типу класса-обертки. Например, попытка упаковать переменную типа byte в Short без предварительного явного приведения byte в short вызовет ошибку компиляции.
Автоупаковка констант примитивных типов допускает более широкие границы соответствия.
В этом случае компилятор способен предварительно осуществлять неявное расширение/сужение типа примитивов:
•
неявное расширение/сужение исходного типа примитива до типа примитива соответствующего классу-обертке (для преобразования int в Byte сначала компилятор самостоятельно неявно сужает int к byte);
•
автоупаковку примитива в соответствующий класс-обертку. Однако, в этом случае существуют два дополнительных ограничения: a) присвоение примитива обертке может производится только оператором = (нельзя передать такой примитив в параметры метода без явного приведения типов) b) тип левого операнда не должен быть старше чем Character, тип правого не должен старше, чем int:
допустимо расширение/сужение byte в/из short, byte в/из char, short в/из char и только сужение byte из int, short из int, char из int. Все остальные варианты требуют явного приведения типов).
Дополнительной особенностью целочисленных классов-оберток, созданных автоупаковкой констант в диапазоне -128 ... +127, является то, что они кешируются JVM. Поэтому такие обертки с одинаковыми значениями будут являться ссылками на один объект.
Что такое явное и неявное приведение типов? В каких случаях в java нужно
использовать явное приведение?
Java является строго типизированным языком программирования, а это означает, что каждое выражение и каждая переменная имеет строго определенный тип уже на момент компиляции. Однако определен механизм приведения типов (casting) – способ преобразования значения переменной одного типа в значение другого типа.
В Java существуют несколько разновидностей приведения:
Тождественное (identity). Преобразование выражения любого типа к точно такому же типу всегда допустимо и происходит автоматически.
Расширение (повышение, upcasting) примитивного типа (widening primitive). Означает, что осуществляется переход от менее емкого типа к более емкому. Например, от типа byte
(длина 1 байт) к типу int (длина 4 байта). Такие преобразование безопасны в том смысле, что новый тип всегда гарантировано вмещает в себя все данные, которые хранились в старом типе, и таким образом не происходит потери данных. Этот тип приведения всегда допустим и происходит автоматически.
Сужение (понижение, downcasting) примитивного типа (narrowing primitive). Означает, что переход осуществляется от более емкого типа к менее емкому. При таком преобразовании есть риск потерять данные. Например, если число типа int было больше 127, то при приведении его к byte значения битов старше восьмого будут потеряны. В Java такое преобразование должно совершаться явным образом, при этом все старшие биты, не умещающиеся в новом типе, просто отбрасываются – никакого округления или других действий для получения более корректного результата не производится.
Расширение объектного типа (widening reference). Означает неявное восходящее приведение типов или переход от более конкретного типа к менее конкретному, т. е. переход от потомка к предку. Разрешено всегда и происходит автоматически.
Сужение объектного типа (narrowing reference). Означает нисходящее приведение, то есть приведение от предка к потомку (подтипу). Возможно только если исходная переменная является подтипом приводимого типа. При несоответствии типов в момент выполнения выбрасывается исключение ClassCastException. Требует явного указания типа.
Преобразование к строке (to String). Любой тип может быть приведен к строке, т. е. к экземпляру класса String.
Запрещенные преобразования (forbidden). Не все приведения между произвольными типами допустимы. Например, к запрещенным преобразованиям относятся приведения от
При приведении ссылочных типов с самим объектом ничего не происходит, меняется лишь тип ссылки, через которую происходит обращение к объекту.
Для проверки возможности приведения нужно воспользоваться оператором instanceof:
Parent parent = new Child();
if (parent instanceof Child) {
Child child = (Child) parent;
}
Когда в приложении может быть выброшено исключение
ClassCastException?
ClassCastException (потомок RuntimeException) – исключение, которое будет выброшено при ошибке приведения типа.
Что такое пул интов?
В классе-обертке Integer есть внутренний класс IntegerCache – пул (pool) целых чисел в промежутке [-128; 127], так как это самый часто встречающийся диапазон. Он объявлен как private static. В этом внутреннем классе кешированные объекты находятся в массиве cache[].
Кеширование выполняется при первом использовании класса-обертки. После первого использования вместо создания нового экземпляра (кроме использования конструктора),
используются кешированные объекты, JVM берет их из пула.
Можно ли изменить размер пула int?
Не из кода, а в параметре JVM.
Какие еще есть пулы примитивов?
У всех целочисленных и char, но размеры изменять нельзя, можно только у int.
Какие есть особенности класса String?
•
это неизменяемый (immutable) и финализированный тип данных;
•
все объекты класса String JVM хранит в пуле строк;
•
объект класса String можно получить, используя двойные кавычки;
•
можно использовать оператор + для конкатенации строк;
•
начиная с Java 7, строки можно использовать в конструкции switch.
Что такое «пул строк»?
Пул строк – это набор строк, хранящийся в Heap.
•
пул строк возможен благодаря неизменяемости строк в Java и реализации идеи интернирования строк;
•
пул строк помогает экономить память, но по этой же причине создание строки занимает больше времени;
•
если для создания строки используются ", то сначала ищется строка в пуле с таким же значением, если находится, то просто возвращается ссылка, иначе создается новая строка в пуле, а затем возвращается ссылка на нее;
•
при использовании оператора new создается новый объект String, затем при помощи метода intern() эту строку можно поместить в пул или же получить из пула ссылку на другой объект String с таким же значением;
•
пул строк является примером паттерна «Приспособленец» (Flyweight).
Почему не рекомендуется изменять строки в цикле? Что рекомендуется
использовать?
Строка – неизменяемый класс, поэтому растет потребление ресурсов при редактировании,
т. к. при каждой итерации будет создаваться новый объект строки. Рекомендуется
использовать StringBuilder.
Почему char[] предпочтительнее String для хранения пароля?
С момента создания строка остается в пуле до тех пор, пока не будет удалена сборщиком мусора. Поэтому даже после окончания использования пароля он некоторое время продолжает оставаться доступным в памяти и способа избежать этого не существует. Это представляет определенный риск для безопасности, поскольку кто-либо, имеющий доступ к памяти, сможет найти пароль в виде текста. В случае использования массива символов для хранения пароля имеется возможность очистить его сразу по окончанию работы с паролем,
позволяя избежать риска безопасности, свойственного строке.
Почему String неизменяемый и финализированный класс?
Есть несколько преимуществ в неизменности строк:
•
Пул строк возможен только потому, что строка неизменяемая, таким образом
виртуальная машина сохраняет больше свободного места в Heap, поскольку разные строковые переменные указывают на одну и ту же переменную в пуле.
Если бы строка была изменяемой, то интернирование строк не было бы возможным, потому что изменение значения одной переменной отразилось бы также и на остальных переменных, ссылающихся на эту строку.
•
Если строка будет изменяемой, тогда это станет серьезной угрозой
1 2 3 4 5 6 7 8 9 ... 25
безопасности
приложения. Например, имя пользователя базы данных и пароль передаются строкой для получения соединения с базой данных и в программировании сокетов реквизиты хоста и порта передаются строкой. Так как строка неизменяемая, ее
значение не может быть изменено, в противном случае злоумышленник может изменить значение ссылки и вызвать проблемы в безопасности приложения.
•
Неизменяемость позволяет избежать синхронизации: строки безопасны для
многопоточности, и один экземпляр строки может быть совместно использован различными потоками.
•
Строки используются classloader, и неизменность обеспечивает правильность
загрузки класса.
•
Поскольку строка неизменяемая, ее hashCode() кешируется в момент создания и нет необходимости рассчитывать его снова. Это делает строку отличным кандидатом для ключа в HashMap, т. к. его обработка происходит быстрее.
Почему строка является популярным ключом в HashMap в Java?
Поскольку строки неизменяемы, их хеш-код вычисляется и кешируется в момент создания,
не требуя повторного пересчета при дальнейшем использовании. Поэтому в качестве ключа
HashMap они будут обрабатываться быстрее.
Что делает метод intern() в классе String?
Метод intern() используется для сохранения строки в пуле строк или получения ссылки, если такая строка уже находится в пуле.
Можно ли использовать строки в конструкции switch?
Да, начиная с Java 7 в операторе switch можно использовать строки, ранние версии Java не поддерживают этого. При этом:
● участвующие строки чувствительны к регистру;
● используется метод equals() для сравнения полученного значения со значениями case, поэтому во избежание NullPointerException стоит предусмотреть проверку на
null;
● согласно документации Java 7 для строк в switch компилятор Java формирует более эффективный байт-код для строк в конструкции switch, чем для сцепленных условий if-else.
Какая основная разница между String, StringBuffer, StringBuilder?
Класс String является неизменяемым (immutable) – модифицировать объект такого класса нельзя, можно лишь заменить его созданием нового экземпляра.
Класс StringBuffer изменяемый – использовать StringBuffer следует тогда, когда необходимо часто модифицировать содержимое.
Класс StringBuilder был добавлен в Java 5, он во всем идентичен классу StringBuffer за исключением того, что он не синхронизирован и поэтому его методы выполняются
значительно быстрей.
Что такое StringJoiner?
Класс StringJoiner используется, чтобы создать последовательность строк, разделенных разделителем с возможностью присоединить к полученной строке префикс и суффикс:
StringJoiner joiner = new StringJoiner(".", "prefix-", "-suffix");
for (String s : "Hello the brave world".split(" ")) {
joiner.add(s);
}
System.out.println(joiner); //prefix-Hello.the.brave.world-suffix
Существуют ли в Java многомерные массивы?
Да (спорно). Тип данных массива – ссылочный. Массив подразумевает непрерывное хранение в памяти, все вложенные массивы будут одинаковыми, под них выделена одинаковая память, это структура, под которую выделяется объем памяти, поэтому нужно знать заранее, какой объем будет у массива.
Какими значениями инициируются переменные по умолчанию?
•
byte, short, int – 0;
•
long – 0L;
•
float – 0.0f;
•
double – 0.0d;
•
char – '\u0000' (символ конца строки);
•
boolean – false (зависит от реализации, можно установить true по умолчанию);
•
объекты – null (это ссылка никуда не указывает, спецуказатель);
•
локальные (в методе) переменные не имеют значений по умолчанию, их имеют поля класса;
•
не static-поле класса будет инициализировано после того, как будет создан объект этого класса, а static-поле будет инициализировано тогда, когда класс будет загружен JVM;
•
сколько весит ссылка в Java: на 32 бит – 4 байта, на 64 бит – 8 байт (но вроде как есть 4 байта) (железо + JVM);
•
заголовок объекта – 1 бит.
Что такое сигнатура метода?
Это имя метода плюс параметры (порядок параметров имеет значение из-за множественной передачи данных через троеточие, которое должно располагаться последним). В сигнатуру метода не входит возвращаемое значение, а также бросаемые им исключения.
Сигнатура метода в сочетании с типом возвращаемого значения и бросаемыми исключениями называется контрактом метода.
От модификатора до выбрасываемого исключения – это контракт.
Расскажите про метод main
Является, как правило, точкой входа в программу и вызывается JVM.
Как только заканчивается выполнение метода main(), так сразу же завершается работа
самой программы.
static – чтобы JVM смогла загрузить его во время компиляции.
public static void и сигнатура – обязательное декларирование.
Мэйнов может быть много и может не быть вообще.
Может быть перегружен.
Каким образом переменные передаются в методы, по значению или по
ссылке?
В Java параметры всегда передаются только по значению, что определяется как
«скопировать значение и передать копию». С примитивами это будет копия содержимого. Со ссылками – тоже копия содержимого, т. е. копия ссылки. При этом внутренние члены ссылочных типов через такую копию изменить возможно, а вот саму ссылку, указывающую на экземпляр – нет.
Массив – это объект.
Если передать массив и изменить его в методе, то будет ли изменяться
текущий массив?
Да, текущий массив изменится тоже.
Какие типы классов есть в Java?
•
Top level class (обычный класс):
◦ Abstract class (абстрактный класс);
◦ Final class (финализированный класс).
•
Interfaces (интерфейс).
•
Enum (перечисление).
•
Nested class (вложенный класс):
◦ Static nested class (статический вложенный класс);
◦ Member inner class (простой внутренний класс);
◦ Local inner class (локальный класс);
◦ Anonymous inner class (анонимный класс).
Расскажите про вложенные классы. В каких случаях они применяются?
Класс называется вложенным (Nested class), если он определен внутри другого класса.
Вложенный класс должен создаваться только для того, чтобы обслуживать обрамляющий его класс. Если вложенный класс оказывается полезен в каком-либо ином контексте, он должен стать классом верхнего уровня. Вложенные классы имеют доступ ко всем (в том числе приватным) полям и методам внешнего класса, но не наоборот. Из-за этого разрешения использование вложенных классов приводит к некоторому нарушению инкапсуляции.
Вложенные классы делятся на две категории: статические и нестатические. Объявленные вложенные классы static называются статическими вложенными классами. Нестатические вложенные классы называются внутренними классами.
Внутренние классы ассоциируются не с внешним классом, а с экземпляром внешнего.
1. Статические вложенные классы (Static nested classes)
•
есть возможность обращения к внутренним статическим полям и методам класса- обертки;
•
обрамляющий класс не имеет доступа к статическим полям;
•
из статического вложенного класса не имеем доступ к статическим полям внешнего класса.
2. Вложенные классы бывают двух видов (Inner или Non-static Nested)
•
есть возможность обращения к внутренним полям и методам класса-обертки;
•
не может иметь статических объявлений;
•
внутри такого класса нельзя объявить перечисления;
•
если нужно явно получить this внешнего класса – OuterClass.this.
3. Локальные классы
•
видны только в пределах блока, в котором объявлены;
•
не могут быть объявлены как private/public/protected или static (по этой причине интерфейсы нельзя объявить локально);
•
не могут иметь внутри себя статических объявлений (полей, методов, классов), но могут иметь константы (static final);
•
имеют доступ к полям и методам обрамляющего класса;
•
можно обращаться к локальным переменным и параметрам метода, если они объявлены с модификатором final или являются effectively final.
4. Анонимные классы
•
локальный класс без имени;
•
создается, чтобы его сразу же применить.
Вложенный класс – это итератор внутри коллекции.
Если связь между объектом внутреннего класса и объектом внешнего класса не нужна,
можно сделать внутренний класс статическим (static). Такой класс называют вложенным
(nested).
Применение статического внутреннего класса означает следующее:
•
для создания объекта статического внутреннего класса не нужен объект внешнего класса;
•
из объекта вложенного класса нельзя обращаться к нестатическим членам внешнего класса.
Каждый тип класса имеет рекомендации по своему применению:
•
нестатический: если вложенный класс должен быть виден за пределами одного метода или он слишком длинный для того, чтобы его можно было удобно разместить в границах одного метода и если каждому экземпляру такого класса необходима ссылка на включающий его экземпляр;
•
статический: если ссылка на обрамляющий класс не требуется;
•
локальный: если класс необходим только внутри какого-то метода и требуется создавать экземпляры этого класса только в этом методе;
•
анонимный: если к тому же применение класса сводится к использованию лишь в одном месте и уже существует тип, характеризующий этот класс.
Что такое «статический класс»?
Это вложенный класс, объявленный с использованием ключевого слова static. К классам верхнего уровня модификатор static неприменим.
Какие существуют особенности использования вложенных классов:
статических и внутренних? В чем заключается разница между ними?
Вложенные классы могут обращаться ко всем членам обрамляющего класса, в том числе и приватным.
Для создания объекта статического вложенного класса объект внешнего класса не требуется.
Из объекта статического вложенного класса нельзя обращаться к не статическим членам обрамляющего класса напрямую, а только через ссылку на экземпляр внешнего класса.
Обычные вложенные классы не могут содержать статических методов, блоков инициализации и классов. Статические вложенные классы могут.
В объекте обычного вложенного класса хранится ссылка на объект внешнего класса. Внутри статического такой ссылки нет. Доступ к экземпляру обрамляющего класса осуществляется через указание .this после его имени. Например: Outer.this.
Что такое «локальный класс»? Каковы его особенности?
Local inner class (локальный класс) – это вложенный класс, который может быть декларирован в любом блоке, в котором разрешается декларировать переменные. Как и простые внутренние классы (member inner class), локальные классы имеют имена и могут использоваться многократно. Как и анонимные классы, они имеют окружающий их экземпляр только тогда, когда применяются в нестатическом контексте.
Локальные классы имеют следующие особенности:
•
видны только в пределах блока, в котором объявлены;
•
не могут быть объявлены как private/public/protected или static;
•
не могут иметь внутри себя статических объявлений (полей, методов, классов);
•
имеют доступ к полям и методам обрамляющего класса;
•
могут обращаться к локальным переменным и параметрам метода, если они объявлены с модификатором final.
Что такое «анонимные классы»? Где они применяются?
Это вложенный локальный класс без имени, который разрешено декларировать в любом месте обрамляющего класса, разрешающем размещение выражений. Создание экземпляра анонимного класса происходит одновременно с его объявлением. В зависимости от местоположения анонимный класс ведет себя как статический либо как нестатический вложенный класс – в нестатическом контексте появляется окружающий его экземпляр.
Анонимные классы имеют несколько ограничений:
•
использование разрешено только в одном месте программы – месте его создания;
•
применение возможно только в том случае, если после порождения экземпляра нет необходимости на него ссылаться;
•
реализует лишь методы своего интерфейса или суперкласса, т. е. не может объявлять каких-либо новых методов, так как для доступа к ним нет поименованного типа.
Анонимные классы обычно применяются для:
•
создания объекта функции (function object), например, реализация интерфейса
Comparator;
•
создания объекта процесса (process object), такого как экземпляры классов
Thread, Runnable и подобных;
•
в статическом методе генерации;
•
инициализации открытого статического поля final, которое соответствует сложному перечислению типов, когда для каждого экземпляра в перечислении требуется отдельный подкласс.
Каким образом из вложенного класса получить доступ к полю внешнего
класса?
Статический вложенный класс имеет прямой доступ только к статическим полям обрамляющего класса.
Простой внутренний класс может обратиться к любому полю внешнего класса напрямую. В
случае, если у вложенного класса уже существует поле с таким же литералом, то обращаться к такому полю следует через ссылку на его экземпляр. Например: Outer.this.field.
Что такое перечисления (enum)?
Перечисления представляют набор логически связанных констант.
Перечисление фактически представляет новый класс, поэтому можно определить переменную данного типа и использовать ее.
Перечисления, как и обычные классы, могут определять конструкторы, поля и методы.
При этом конструктор по умолчанию приватный. Также можно определять методы для отдельных констант.
Можно создавать публичные геттеры\сеттеры. Они создаются в момент компиляции.
Методы:
•
valueOf() возвращает конкретный элемент;
•
ordinal() возвращает порядковый номер определенной константы (нумерация начинается с 0);
•
values() возвращает массив всех констант перечисления;
•
name() отличается от toString тем, что второй можно переопределить.
В Енам реализация equals() через ==, поэтому можно и через equals(), и через ==.
Еnum имеет ряд преимуществ при использовании в сравнении с static final int.
Главным отличием является то, что, используя enum, можно проверить тип данных.
Недостатки:
•
не применимы операторы >, <, >=, <=;
•
требует больше памяти для хранения, чем обычная константа.
Нужны для ограничения области допустимых значений: например, времена года, дни недели.
Особенности Enum-классов
•
Конструктор всегда private или default.
•
Могут имплементировать интерфейсы.
•
Не могут наследовать класс.
•
Можно переопределить toString().
•
Нет public конструктора, поэтому нельзя создать экземпляр вне Enum.
•
При equals() выполняется ==.
•
ordinal() возвращает порядок элементов.
•
Может использоваться в TreeSet и TreeMap, т. к. Enum имплементирует
Comparable.
•
compareTo() имитирует порядок элементов, предоставляемый ordinal().
•
Можно использовать в Switch Case.
•
values() возвращает массив всех констант.
•
Легко создать потокобезопасный singleton без double check volatile переменных.
Ромбовидное наследование
Ромбовидное наследование (англ. diamond inheritance) – ситуация в объектно- ориентированных языках программирования с поддержкой множественного наследования,
когда два класса B и C наследуют от A, а класс D наследует от обоих классов B и C. При этой схеме наследования может возникнуть неоднозначность: если объект класса D
вызывает метод, определенный в классе A (и этот метод не был переопределен в классе D),
а классы B и C по-своему переопределили этот метод, то от какого класса его наследовать:
B или C?
Как проблема ромбовидного наследования решена в java?
В Java нет поддержки множественного наследования классов.
Предположим, что SuperClass – это абстрактный класс, описывающий некоторый метод, а классы ClassA и ClassB – обычные классы наследники SuperClass, а класс ClassC
наследуется от ClassA и ClassB одновременно. Вызов метода родительского класса приведет к неопределенности, так как компилятор не знает о том, метод какого именно суперкласса должен быть вызван. Это и есть основная причина, почему в Java нет поддержки множественного наследования классов. Интерфейсы – это только резервирование/описание метода, а реализация самого метода будет в конкретном классе,
реализующем эти интерфейсы, таким образом исключается неопределенность при множественном наследовании интерфейсов. В случае, если вызывается default-метод из интерфейса его обязательно надо будет переопределить.