А если переменная — это объект?
С объектами чуть хитрее. Смотри пример:
public class Example {
private int number = 2;
public static void main(String[] args) {
Example object = new Example();
System.out.println(object.number);
}
}
Что тут происходит? Во-первых, JVM создаёт фрейм для метода
main в стеке. Потом видит
new Example() — и создаёт объект. Но не в стеке, а в
Heap (куче).
В стеке хранится только ссылка на этот объект. Грубо говоря, адрес, где его найти. А сам объект со всеми его полями живёт в куче.
Метод
main завершается, фрейм из стека удаляется. Ссылка пропадает, но объект-то остался в куче. На него теперь никто не ссылается — и тут в дело вступает
сборщик мусора. Он находит такие "потерянные" объекты и удаляет их. Автоматически, без твоего участия.
Поля класса — отдельная история
Поля класса работают не так, как локальные переменные. Посмотри:
public class Example {
private int number;
private static int count;
public static void main(String[] args) {
Example object = new Example();
System.out.println(object.number);
System.out.println(count);
}
}
Заметил? Мы не присвоили никаких значений, а код работает. Выводит нули. Это потому что
поля класса получают значения по умолчанию:
- Числа — это
0 или
0.0
-
boolean — это
false
- Объекты — это
null
Есть важное различие между обычными и статическими полями.
Обычное поле number создаётся, когда создаёшь объект (
new Example()).
Статическое поле count создаётся раньше — когда JVM загружает сам класс в память. Это происходит до создания любых объектов.
Где живут статические переменные
Статические переменные — часть самого класса, его метаданные. С версии Java 8 они хранятся в
Metaspace. Раньше была область PermGen, но её убрали. Metaspace — это Non-Heap память, кстати.
Порядок имеет значение
Нельзя использовать переменную до её объявления. Вот такой код не скомпилируется:
public class Example {
private static int b = a;
private static int a = 1;
public static void main(String[] args) {
System.out.println(b);
}
}
Ошибка, потому что пытаемся взять значение
a, которая объявлена позже.
Что за зверь этот null
Для объектных переменных значение по умолчанию —
null. Это не объект и не число. Это вообще специальное значение, которое означает "тут ничего нет".
public class Example {
private String text;
public static void main(String[] args) {
Example object = new Example();
System.out.println(object.text);
}
}
null — это литерал, который показывает отсутствие ссылки. Попробуешь вызвать метод на
null — получишь
NullPointerException:
String text = null;
System.out.println(text.length());
Эта ошибка встречается постоянно, особенно на первых порах. Привыкай проверять переменные на
null там, где это нужно.
Кстати, про var
С Java 10 появилось ключевое слово
var. Оно позволяет не писать тип явно — компилятор сам разберётся:
var number = 10;
var text = "Hello";
var list = new ArrayList();
Удобно, правда? Но есть ограничения.
var работает только для локальных переменных, и только если ты сразу даёшь значение. Нельзя так:
var x;
var y = null;
Что важно запомнить
Локальные переменные надо инициализировать. Обязательно. Иначе компилятор не пропустит код.
Поля класса инициализируются автоматически — числа получают 0, boolean получает false, объекты получают null.
Примитивы живут в стеке (если это локальные переменные) или внутри объекта в куче (если это поля).
Объекты всегда создаются в куче. В стеке хранятся только ссылки на них.
Статические переменные создаются при загрузке класса и хранятся в Metaspace.
Сборщик мусора сам удаляет объекты, на которые больше никто не ссылается. Не нужно делать это вручную.
С Java 10 можешь использовать
var для локальных переменных — компилятор сам определит тип.
Вообще, понимание работы с памятью в Java — это база. Сначала кажется сложным, но когда разберёшься — многие вещи станут логичными. Ты начнёшь понимать, почему код ведёт себя так, а не иначе, и где искать проблемы.