JavaRush /Java Blog /Random-ID /Присваивание и инициализация в Java
Viacheslav
Level 3

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

Dipublikasikan di grup Random-ID

Вступление

Основное преднаmeaning компьютерных программ - обработка данных. Whatбы обработать данные нужно их How-то хранить. Предлагаю разобраться с тем, How происходит хранение данных.
Присваивание и инициализация в Java - 1

Переменные

Переменные (variables) - это контейнеры, которые хранят Howие-либо данные. Посмотрим официальный Tutorial от Oracle : Declaring Member Variables. Согласно данному Tutorial, существует несколько типов переменных:
  • Поля (fields) : переменные, объявленные в классе;
  • Локальные переменные (local variables) : переменные в методе or в code block;
  • Параметры (parameters) : переменные в объявлении метода (в сигнатуре).
Все переменные должны иметь тип переменной и название переменной.
  • Тип переменной показывает, Howие данные представляет данная переменная (т.е. Howие данные может хранить). Как мы знаем, тип переменной может быть примитивным (primitives primitives) or an objectным, не примитивными (Non-primitive). При an objectных переменных их тип описывается определённым классом.
  • Название переменной должно быть с маленькой буквы, в camel case. Подробнее про именование можно прочитать в "Variables:Naming".
Так же если переменная уровня класса, т.е. является полем класса, то для неё может указываться модификатор доступа. Подробнее см. Controlling Access to Members of a Class.

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

Итак, мы вспомнor, что такое переменная. Для того, чтобы с переменной начать работать нужно её объявить. Для начала, разберёмся с локальной переменной. Вместо IDE для удобства воспользуемся онлайн решением от tutorialspoint: Online IDE. Выполним в их online IDE вот такую простенькую программку:

public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
Итак, How видно, мы объявor локальную переменную с именем number и типом int. Нажимаем кнопку «Execute» и получаем ошибку:

HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
What же произошло? Мы объявor переменную, но meaning её не инициализировали. Стоит заметить, что ошибка эта произошла не в момент выполнения (т.е. не в Runtime), а в момент компиляции. Умный компилятор проверил, будет ли локальная переменная инициализирована до обращения к ней or нет. Поэтому, из этого следует следующие утверждения:
  • Обращение к локальным переменным должно быть выполнено только после того, How они будут инициализированы;
  • Локальные переменные не имеют значений по умолчанию;
  • Проверка значений локальных переменных выполняется в момент компиляции.
Итак, нам говорят, что переменная должна быть проинициализирована. Инициализация переменной – присвоение переменной значения. Давайте тогда разбираться, что это и почему.

Инициализация локальной переменной

Инициализация переменных одна из самых мудрёных тем в Java, т.к. очень тесно связана с работой с памятью, с реализацией JVM, спецификацией JVM и другими не менее страшными и хитрыми вещами. Но можно попробовать разобраться хоть в Howой-то мере. Пойдём от простого к сложному. Whatбы инициализировать переменную воспользуемся оператором присваивания и изменим строчку в нашем прошлом codeе:

int number = 2;
В таком варианте ошибок не будет и на экран выведется meaning. What же происходит в этом случае? Давайте попробуем порассуждать. Если мы хотим присвоить переменной Howое-то meaning, значит мы хотим, чтобы эта переменная хранила meaning. Получается, что meaning где-то должно храниться, но где? На диске? Но это очень медленно и может на нас накладывать ограничения. Получается, единственное, где мы можем быстро и эффективно хранить данные «здесь и сейчас» это память. Значит, нам нужно выделить в памяти Howое-то место. Так и есть. При инициализации переменной под неё будет выделено место в памяти, отведённой java процессу, в рамках которого будет выполняться наша программа. Память, выделяемая java процессу, разделена на несколько областей or зон. В Howой из них будет выделено место зависит от того, Howого типа была объявлена переменная. Память разделяется на следующие разделы: Heap, Stack и Non-Heap. Начнём со стэковой памяти. Stack переводится How стопка (например, стопка книг). Представляет собой LIFO структуру данных (Last In, First Out). То есть How стопка книг. Когда мы добавляем в неё книги – мы кладём их сверху, а когда забираем – берём верхнюю (т.е. ту, которая добавлена самой последней). Итак, мы запускаем нашу программу. Как мы знаем, Java программу выполняет JVM, то есть виртуальная Java машина. JVM должна знать то, откуда должно начаться выполнение программы. Для этого мы объявляем main метод, который называется «точкой входа». Для выполнения в JVM создаётся основной поток (Thread). При создании потока ему выделяется свой стэк в памяти. Этот стэк состоит из фрэймов. При выполнении каждого нового метода в потоке под него будет выделен новый фрэйм и добавлен на вершину стэка (How новая книжка в стопке книг). Этот фрэйм будет содержит ссылки на an objectы и примитивные типы. Да да, наш int будет храниться в стэке, т.к. int это примитивный тип. Прежде чем выделить фрэйм JVM должна понимать, что туда сохранять. Именно по этой причине мы получим ошибку «variable might not have been initialized», ведь если она не инициализирована, то JVM не сможет нам подготовить стэк. Поэтому при компиляции программы умный компилятор поможет нам не допустить ошибку и не сломать всё. (!) Для наглядности советую супер-пупер статью: "Java Stack and Heap: Java Memory Allocation Tutorial". В ней ссылаются на не менее крутое видео:
После завершения выполнения метода из стэка потока будут удаляться фрэймы, выделенные под эти методы, а вместе с ними и очищаться память, выделенная под этот фрэйм со всеми данными.

Инициализация локальных an objectных переменных

Давайте опять изменим наш code на чуть более хитрый:

public class HelloWorld{

    private int number = 2;
    
    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
    
}
What же тут будет происходить? Давайте ещё раз рассуждать. JVM узнает о том, откуда ей выполнять программу, т.е. она видит main метод. Она создаёт поток, под него выделяет память (потоку ведь надо где-то хранить данные, которые нужны для выполнения). В этом потоке выделяется фрэйм под метод main. Далее мы создаём an object HelloWorld. Этот an object уже создаётся не в стэке, а в хипе. Потому что object у нас не примитивный тип, а an objectный. А в стэке будет храниться только link на an object в хипе (мы ведь How-то должны обращаться к этому an objectу). Далее в стэке метода main будут выделены фрэймы для выполнения метода println. После выполнения метода main будут уничтожены все фрэймы. При уничтожении фрэйма будут уничтожены все данные. Объект object не будет уничтожен сразу. Сначала на него будет уничтожена link и таким образом на an object object больше никто ссылаться не будет и доступа больше к этому an objectу в памяти будет не получить. Умная JVM имеет свой механизм для такого – сборщик мусора (garbage collector or сокращённо GC). Он то и удаляет из памяти такие an objectы, на которые больше никто не ссылается. Данный процесс опять же был описан в ссылке, что была приведена выше. Там даже видео есть с объяснением.

Инициализация полей

Инициализация полей, указанных в классе происходит особым образом в зависимости от того, является ли поле статическим or нет. Если у поля стоит ключевое слово static, то данное поле относится к самому классу, а не слово static не указано, то данное поле относится к экземпляру класса. Давайте рассмотрим это на примере:

public class HelloWorld{
    private int number;
    private static int count;
    
    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
В данном примере, инициализация полей происходит в разное время. Поле number будет инициализировано после того, How будет создан an object object класса HelloWorld. А вот поле count будет инициализировано тогда, когда класс будет загружен виртуальной Java machine. Загрузка классов – это отдельная тема, поэтому не будем сюда примешивать её. Просто стоит знать, что статические переменные инициализируются тогда, когда о классе становится известно при выполнении. Тут важнее другое и Вы уже это заметor. Мы нигде не указали значения, а оно работает. И действительно. Переменные, которые являются полями, если для них не указано meaning, то они инициализируются meaningм по умолчанию. Для числовых meaningм это 0 or 0.0 для чисел с плавающей точкой. Для boolean это false. А для всех переменных an objectных типов meaning будет null (об этом мы ещё поговорим). Казалось бы, а почему так? А потому, что an objectы создаются в Heap (в куче). Работа с данной областью выполняется в Runtime. И мы в runtime можем инициализировать эти переменные, в отличии от стэка, память под который должна быть подготовлена ещё до выполнения. Так устроена работа с памятью в Java. Но есть тут и ещё одна особенность. В этом маленьком кусочке затрагиваются разные уголки памяти. Как мы помним, в Stack памяти под метод main выделяется фрэйм. В этом фрэйме хранится link (reference) на an object в Heap памяти. Но где тогда хранится count? Как мы помним, эта переменная инициализируется сразу, до создания an object в хипе. Вот тут действительно хитрый вопрос. До Java 8 существовала область памяти, называемая PERMGEN. Начиная с Java 8 эта область претерпела изменения и называется METASPACE. По сути, статические переменные являются частью описания класса, т.е. его метаданными. Поэтому, логично, что хранится в хранorще метаданных, METASPACE. MetaSpace относится к той самой Non-Heap области памяти, является её частью. Важно ещё учитывать то, что учитывается порядок, в котором объявлены переменные. Например, в этом codeе ошибка:

public class HelloWorld{
    
    private static int b = a;
    private static int a = 1;
    
    public static void main(String []args){
        System.out.println(b);
    }
    
}

What такое null

Как было сказано выше, переменные an objectных типов, если они являются полями класса, инициализируются значениями по умолчанию и таким meaningм по умолчанию является null. Но что же такое null в Java? Первое что важно помнить – примитивные типы не могут быть null. А всё потому, что null – это особенная link (reference), которая не ссылается никуда, ни на Howой an object. Поэтому, только an objectная переменная может быть равна null. Второе, что важно понимать, что null – это link, reference. Я reference тоже имеют свой вес. На эту тему можно почитать вопрос на stackoverflow: "Does null variable require space in memory".

Блоки инициализации

Рассматривая инициализацию переменных грех не рассмотреть блоки инициализации. Выглядит это следующим образом:

public class HelloWorld{
    
    static {
        System.out.println("static block");
    }
    
    {
        System.out.println("block");
    }
    
    public HelloWorld () {
        System.out.println("Constructor");
    }
    
    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }
    
}
Порядок вывода будет: static block, block, Constructor. Как мы видим, блоки инициализации выполняются раньше, чем конструктор. И иногда это может быть удобным средством для инициализации.

Заключение

Надеюсь, этот небольшое обзор смог привнести понимание того, How это работает и почему. #Viacheslav
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION