С этих вопросов начинается почти любой собес на Java: что такое примитив, чем int отличается от Integer, почему == иногда «работает» на объектах и в каком порядке инициализируется класс.
Восемь примитивов
| Тип | Размер | Значение по умолчанию (в поле) |
|---|---|---|
byte | 8 бит | 0 |
short | 16 бит | 0 |
int | 32 бита | 0 |
long | 64 бита | 0L |
float | 32 бита | 0.0f |
double | 64 бита | 0.0 |
char | 16 бит, беззнаковый (UTF-16) | '\u0000' |
boolean | размер не задан спецификацией | false |
Локальные переменные значений по умолчанию не получают: компилятор не даст прочитать неинициализированную.
Целочисленное переполнение молчит: Integer.MAX_VALUE + 1 равно Integer.MIN_VALUE. Если нужно исключение, есть Math.addExact. Деньги не считают в double: 0.1 + 0.2 даёт 0.30000000000000004, для денег берут BigDecimal или целые копейки в long.
Обёртки и автоупаковка
У каждого примитива есть класс-обёртка: Integer, Long, Character, Boolean... Компилятор сам упаковывает и распаковывает:
Integer a = 5; // Integer.valueOf(5)
int b = a + 1; // a.intValue() + 1
Integer n = null;
int c = n; // NullPointerException при распаковке
Integer.valueOf кеширует значения от -128 до 127 (верхнюю границу можно поднять флагом -XX:AutoBoxCacheMax). Поэтому:
Integer x = 127, y = 127;
System.out.println(x == y); // true: один объект из кеша
Integer p = 128, q = 128;
System.out.println(p == q); // false: два разных объекта
System.out.println(p.equals(q)); // true
Правило: объекты сравнивают через equals, == сравнивает ссылки. Кеш есть также у Byte, Short, Long, Character (0..127) и Boolean, но не у Float и Double.
Передача параметров
В Java всё передаётся по значению. Для объекта значение это копия ссылки: метод может изменить состояние объекта, но не может заставить переменную вызывающего кода указывать на другой объект.
static void change(StringBuilder sb) {
sb.append("!"); // видно снаружи
sb = new StringBuilder(); // снаружи не видно
}
static и final
staticполе одно на класс, а не на объект.staticметод не имеетthisи не переопределяется, а скрывается (method hiding).finalпеременную нельзя переприсвоить,finalметод нельзя переопределить, отfinalкласса нельзя наследоваться.finalссылка не делает объект неизменяемым:final List<String> listможно наполнять.- Константа времени компиляции (
static final int MAX = 10) подставляется в код вызывающих классов. Поменяли значение и не перекомпилировали зависимый модуль: там останется старое.
Порядок инициализации
При первом обращении к классу: статические поля и static-блоки сверху вниз, один раз. При каждом new: сначала конструктор родителя, потом инициализаторы полей и блоки экземпляра сверху вниз, потом тело конструктора.
class A {
static { System.out.println("A static"); }
{ System.out.println("A init"); }
A() { System.out.println("A()"); }
}
class B extends A {
static { System.out.println("B static"); }
{ System.out.println("B init"); }
B() { System.out.println("B()"); }
}
// new B(); new B();
// A static, B static, A init, A(), B init, B(), A init, A(), B init, B()
Частые ошибки
- Сравнивать
IntegerилиStringчерез==и удивляться, что на тестах работало. - Распаковка
nullвint(например,map.get(key)в арифметике). - Ждать, что метод «вернёт» новую ссылку через параметр.
- Вызывать переопределяемый метод из конструктора родителя: подкласс ещё не проинициализировал свои поля.
Что спросят на собесе
- Какие примитивы есть и чем
intотличается отInteger? - Что выведет
Integer a = 128, b = 128; a == bи почему? - Java передаёт объекты по ссылке или по значению?
- В каком порядке выполняются static-блоки, блоки инициализации и конструкторы при наследовании?
- Что значит
finalдля ссылки на коллекцию?