JavaRush/Java блог/Random/Присваивание и инициализация в Java
Viacheslav
14 уровень

Присваивание и инициализация в Java

Статья из группы Random
участников

Зачем вообще нужны переменные

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

Какие бывают переменные в Java

В Java есть три основных типа переменных, и они отличаются тем, где объявлены и как долго живут. Поля класса (их ещё называют fields) — это переменные, которые ты пишешь прямо в классе, но не внутри методов. Они описывают состояние твоего объекта. Локальные переменные — те, что объявляются внутри метода или какого-то блока кода. Как только метод завершается — они исчезают. Параметры — это переменные в скобках при объявлении метода. Они получают значения, когда кто-то вызывает твой метод. Все переменные должны иметь тип переменной и название переменной.
  • Тип переменной показывает, какие данные представляет данная переменная (т.е. какие данные может хранить). Как мы знаем, тип переменной может быть примитивным (primitives primitives) или объектным, не примитивными (Non-primitive). При объектных переменных их тип описывается определённым классом.
  • Название переменной должно быть с маленькой буквы, в camel case. Подробнее про именование можно прочитать в "Variables:Naming".
Так же если переменная уровня класса, т.е. является полем класса, то для неё может указываться модификатор доступа.

Объявление переменной

Объявить переменную — это как сказать компилятору "эй, у меня тут будет переменная с таким-то именем и типом". Смотри, что получается:
public class Example {
    public static void main(String[] args) {
        int number;
        System.out.println(number);
    }
}
Запускаешь — а тебе сразу ошибка в лицо:
error: variable number might not have been initialized
Мы объявили переменную number, но забыли дать ей значение. И компилятор не даёт тебе даже запустить это. Почему? Потому что локальные переменные не получают никаких значений автоматически. Вообще никаких. И это проверяется ещё до запуска программы, на этапе компиляции. Поэтому, из этого следует следующие утверждения:
  • Обращение к локальным переменным должно быть выполнено только после того, как они будут инициализированы;
  • Локальные переменные не имеют значений по умолчанию;
  • Проверка значений локальных переменных выполняется в момент компиляции.
Итак, нам говорят, что переменная должна быть проинициализирована. Инициализация переменной – присвоение переменной значения. Давайте тогда разбираться, что это и почему.

Инициализация — присваиваем значение

Инициализация — это когда ты даёшь переменной начальное значение. Чтобы код заработал, нужен оператор присваивания =:
public class Example {
    public static void main(String[] args) {
        int number = 2;
        System.out.println(number);
    }
}
Теперь всё окей, код работает. Но что там внутри происходит? Давай копнём глубже.

Что творится в памяти

Когда инициализируешь переменную, JVM (это виртуальная машина Java) выделяет под неё место в памяти. Память в Java-процессе разбита на несколько зон — есть Heap, Stack, Metaspace. Начнём со Stack. Представь стопку тарелок — положил одну, потом вторую сверху, потом третью. Когда берёшь — снимаешь верхнюю. Это называется LIFO (Last In, First Out). Запускается программа, JVM видит метод main и создаёт поток (Thread). У каждого потока свой стек. Вызывается метод — создаётся фрейм в стеке, там хранятся все локальные переменные этого метода. Примитивы типа int, double, boolean живут прямо в стеке. Вот почему компилятор требует их инициализировать — JVM должна точно знать, какое значение записать в стек. Метод завершился — фрейм удаляется, переменные пропадают. Всё просто. (!) Для наглядности советую супер-пупер видео:

А если переменная — это объект?

С объектами чуть хитрее. Смотри пример:
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); // Выведет 0
        System.out.println(count); // Выведет 0
    }
}
Заметил? Мы не присвоили никаких значений, а код работает. Выводит нули. Это потому что поля класса получают значения по умолчанию: - Числа — это 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; // По умолчанию null

    public static void main(String[] args) {
        Example object = new Example();
        System.out.println(object.text); // Выведет: null
    }
}
null — это литерал, который показывает отсутствие ссылки. Попробуешь вызвать метод на null — получишь NullPointerException:
String text = null;
System.out.println(text.length()); // Бах! NullPointerException
Эта ошибка встречается постоянно, особенно на первых порах. Привыкай проверять переменные на null там, где это нужно.

Кстати, про var

С Java 10 появилось ключевое слово var. Оно позволяет не писать тип явно — компилятор сам разберётся:
var number = 10; // Это int
var text = "Hello"; // Это String
var list = new ArrayList(); // Компилятор видит тип
Удобно, правда? Но есть ограничения. var работает только для локальных переменных, и только если ты сразу даёшь значение. Нельзя так:
var x; // Ошибка: а какой тип-то?
var y = null; // Ошибка: компилятор не может понять тип

Что важно запомнить

Локальные переменные надо инициализировать. Обязательно. Иначе компилятор не пропустит код. Поля класса инициализируются автоматически — числа получают 0, boolean получает false, объекты получают null. Примитивы живут в стеке (если это локальные переменные) или внутри объекта в куче (если это поля). Объекты всегда создаются в куче. В стеке хранятся только ссылки на них. Статические переменные создаются при загрузке класса и хранятся в Metaspace. Сборщик мусора сам удаляет объекты, на которые больше никто не ссылается. Не нужно делать это вручную. С Java 10 можешь использовать var для локальных переменных — компилятор сам определит тип. Вообще, понимание работы с памятью в Java — это база. Сначала кажется сложным, но когда разберёшься — многие вещи станут логичными. Ты начнёшь понимать, почему код ведёт себя так, а не иначе, и где искать проблемы.
Комментарии (22)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
4 января 2025, 18:11
супер, здорово, прекрасно
🟡ampersand
Уровень 36
16 июня 2022, 09:00
Спасибо ! 🔥
MIke
Уровень 18
24 марта 2021, 09:48
Большое спасибо автору! Статья очень полезная!
barracuda
Уровень 41
Expert
8 февраля 2021, 21:07
Очень хорошо, без погружения в кучу непонятностей. Все по делу. Спасибо, Автор.
Alexander
Уровень 19
6 февраля 2021, 13:47
Статья отличная, большое спасибо автору)
hidden #1708204
Уровень 23
12 декабря 2020, 19:49
Все равно не понятно, почему "Локальные переменные не имеют значений по умолчанию", сделали бы чтобы имели значения по умолчанию и ошибки бы не было на этапе компиляции....
Victoria Bizhunova
Уровень 11
16 августа 2020, 10:58
спасибо, понятная статья
Ivan Romanishin
Уровень 1
27 апреля 2020, 13:20
Не совсем понял про память, чтобы переменная сохранила значение, но где хранила? На диске? Но это очень медленно, получается чтоб использовать значение переменной мы сохраняем его в памяти (да понятно что в памяти, а где еще можна сохранить?). Ну а где эта память физически находиться? Имеется ввиду оперативная память, или кэш процессора ? Или может в САТА шлейфе, тот что с синей обмоткой?
Sherzod
Уровень 1
15 августа 2020, 20:30
он хранится в ОЗУ ОЗУ вот так выглядит
15 декабря 2019, 19:34
А про инициализацию переменных в (сигнатуре) ? Поля и локальные переменные красиво расписал.
Тоня Холод
Уровень 9
28 ноября 2019, 11:21
"Проверка значений локальных переменных выполняется в момент компиляции" - неверно,в момент компиляции только проверяется,чтобы все переменные были инициализированы
Viacheslav
Уровень 14
28 ноября 2019, 20:58
Легко проверить.
public class HelloWorld{
    private int a = 999999999999;
    public static void main(String []args){
        System.out.println("Hello");
    }
}
Запустите на выполнение (например, в Online компиляторе) и посмотрите, кто выдаст ошибку. Вы её получите не из рантайма (не будет данных о потоке).
Тоня Холод
Уровень 9
5 января 2020, 10:57
Ну да, тут компилятор ругается, что переменная имеет недопустимое значение
Переменные типа int способны хранить целые числа в диапазоне от -2 миллиарда до +2 миллиарда. Или, если быть более точным, то от -2,147,483,648 до 2,147,483,647.
Лена Зелёная
Уровень 41
25 октября 2020, 15:35
суть не в самой ошибке, а в её родителе. RuntimeException/CompilationException