Приветствую, коллеги. Решил показать несколько способов инициализации полей в Java. Статья может быть полезна тем, кто только начал изучать программирование. Если нужен короткий ответ, способов три. Значение можно присвоить прямо при объявлении поля, и тогда оно будет одинаковым у всех объектов класса. Можно принимать его в конструкторе, и тогда каждый объект получит свое. А можно вынести общую для всех конструкторов подготовку в блок инициализации. Если не сделать ни того ни другого, поле все равно не останется пустым: Java подставит значение по умолчанию, у ссылочных типов это null, у чисел ноль, у boolean это false.

Кратко

  • Неинициализированное поле получает значение по умолчанию: null у ссылочных типов, 0 у целых, 0.0 у дробных, false у boolean.
  • Первый способ задать значение это присвоить его прямо при объявлении поля. Значение будет одним и тем же у всех объектов.
  • Второй способ это конструктор с параметрами: каждый объект получает свои значения в момент создания.
  • Имена параметров конструктора обычно совпадают с именами полей, поэтому к полям внутри обращаются через this.
  • Третий способ это блок инициализации, фигурные скобки прямо в теле класса. Он выполняется при каждом создании объекта, до тела конструктора.
  • Как только вы объявили хотя бы один конструктор, конструктор без параметров перестает создаваться сам собой. Нужен он, придется написать его руками.
  • С локальными переменными все иначе: значений по умолчанию у них нет, и компилятор потребует присвоить значение до первого использования.
Ломтики хлеба, на которых лазерным гравером выжжена надпись HELLO WORLDДля запуска демонстрационных примеров создадим Java класс Demo с методом main(). В методе main() выведем что-нибудь на экран, чтобы убедиться, что программа запускается:

public class Demo {
    public static void main(String[] args) {
        System.out.println("Hello JavaRush.");
    }
}

Что будет, если поле не инициализировать

Далее создадим Java класс кота, добавим в него несколько полей: имя, возраст.

class Cat {
    String name;
    int age;
}
В методе main() создадим двух котов. Попробуем вывести на экран их имя и возраст:

public class Demo {
    public static void main(String[] args) {
        Cat cat1 = new Cat();
        Cat cat2 = new Cat();

        System.out.println("Имя первого кота: " + cat1.name);
        System.out.println("Возраст первого кота: " + cat1.age);
        System.out.println();
        System.out.println("Имя второго кота: " + cat2.name);
        System.out.println("Возраст второго кота: " + cat2.age);
    }
}

class Cat {
    String name;
    int age;
}
Запустив программу, получим вывод:

Имя первого кота: null
Возраст первого кота: 0

Имя второго кота: null
Возраст второго кота: 0
Так как мы не инициализировали эти поля, они получили значения по умолчанию. String относится к ссылочным типам, а для всех ссылочных типов значение по умолчанию это null. int относится к примитивным типам. У каждого примитивного типа есть свое значение по умолчанию, и все они перечислены в спецификации языка, в разделе Initial Values of Variables:
Тип Значение по умолчанию
byte, short, int, long 0
float, double 0.0
char '\u0000', символ с нулевым кодом
boolean false
Любой ссылочный тип: String, массивы, ваши собственные классы null
Важная оговорка: все сказанное относится именно к полям. У локальных переменных, объявленных внутри метода, значений по умолчанию нет вовсе. Спецификация в том же разделе требует, чтобы локальной переменной явно присвоили значение до первого использования, иначе программа просто не скомпилируется. Как можно передать в наши поля реальные значения (например, имя "Том" и возраст "два года")?

Способ 1: инициализация при объявлении

Самый короткий путь это присвоить значение прямо там, где поле объявлено:

public class Demo {
    public static void main(String[] args) {
        Cat cat1 = new Cat();
        Cat cat2 = new Cat();

        System.out.println("Имя первого кота: " + cat1.name);
        System.out.println("Возраст первого кота: " + cat1.age);
        System.out.println();
        System.out.println("Имя второго кота: " + cat2.name);
        System.out.println("Возраст второго кота: " + cat2.age);
    }
}

class Cat {
    String name = "Том";
    int age = 2;
}
Запускаем программу и видим, что поля инициализированы нашими значениями:

Имя первого кота: Том
Возраст первого кота: 2

Имя второго кота: Том
Возраст второго кота: 2
При этом сколько бы котов мы ни создавали, они все будут иметь одинаковые имя и возраст. То есть мы просто поменяли значения null и 0 на “Том” и 2.

Способ 2: инициализация через конструктор

Создадим коту конструктор с двумя параметрами. Обычно имена параметров делают такими же, как и имена полей. В таком случае для обращения к полям нужно использовать слово this, а к параметрам конструктора можно обращаться обычным способом:

public Cat(String name, int age) {
    this.name = name;
    this.age = age;
}
Еще раз:
Скриншот кода класса Cat с пояснениями: синим отмечены поля name и age, места их объявления и обращения к ним, оранжевым параметры конструктора
Добавим также пустой конструктор без параметров, чтобы наш предыдущий пример был работоспособным. Причина вот в чем: пока в классе не объявлено ни одного конструктора, Java дописывает конструктор без параметров сама, это конструктор по умолчанию. Но стоит объявить свой, хотя бы один, и бесплатный вариант исчезает. Поэтому вызов new Cat() после добавления конструктора с параметрами перестал бы компилироваться, и пустой конструктор приходится писать руками. Подробнее про них есть отдельная статья. В методе main() добавим еще двух котов с помощью конструктора с параметрами.

public class Demo {
    public static void main(String[] args) {
        Cat cat1 = new Cat();
        Cat cat2 = new Cat();

        System.out.println("Имя первого кота: " + cat1.name);
        System.out.println("Возраст первого кота: " + cat1.age);
        System.out.println();
        System.out.println("Имя второго кота: " + cat2.name);
        System.out.println("Возраст второго кота: " + cat2.age);

        Cat cat3 = new Cat("Рыжик", 5);
        Cat cat4 = new Cat("Барсик", 9);

        System.out.println();
        System.out.println("Имя третьего кота: " + cat3.name);
        System.out.println("Возраст третьего кота: " + cat3.age);
        System.out.println();
        System.out.println("Имя четвертого кота: " + cat4.name);
        System.out.println("Возраст четвертого кота: " + cat4.age);
    }
}

class Cat {
    String name;
    int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Cat() {
    }
}
Запустим программу:

Имя первого кота: null
Возраст первого кота: 0

Имя второго кота: null
Возраст второго кота: 0

Имя третьего кота: Рыжик
Возраст третьего кота: 5

Имя четвертого кота: Барсик
Возраст четвертого кота: 9
Как видим, созданные таким способом коты обладают большей индивидуальностью.

Способ 3: блок инициализации

Есть и третий способ, о котором новички обычно узнают позже. Прямо в теле класса можно поставить фигурные скобки и написать внутри любой код. Такой блок называется блоком инициализации и выполняется при каждом создании объекта:

public class Demo {
    public static void main(String[] args) {
        Cat cat5 = new Cat();
        Cat cat6 = new Cat("Мурзик", 3);

        System.out.println("Имя пятого кота: " + cat5.name);
        System.out.println("Возраст пятого кота: " + cat5.age);
        System.out.println();
        System.out.println("Имя шестого кота: " + cat6.name);
        System.out.println("Возраст шестого кота: " + cat6.age);
    }
}

class Cat {
    String name;
    int age;

    {
        name = "Безымянный";
        age = 1;
    }

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Cat() {
    }
}
Запустим программу:

Имя пятого кота: Безымянный
Возраст пятого кота: 1

Имя шестого кота: Мурзик
Возраст шестого кота: 3
Пятый кот получил имя из блока, а шестому конструктор перезаписал значения своими. Так и работает порядок: сначала выполняются блоки инициализации, потом тело конструктора. Спецификация описывает это как отдельный шаг создания объекта, который идет сразу после вызова конструктора родителя и до остального кода конструктора. Зачем это нужно, если есть конструктор? Затем, что конструкторов у класса может быть несколько, и повторять в каждом одну и ту же подготовку неудобно. Общую часть выносят в блок, и она отработает при любом способе создания объекта. У блока есть и статический вариант, со словом static перед скобками. Он выполняется один раз, когда класс загружается, и нужен для статических полей, значение которых не помещается в одно выражение:

import java.util.Arrays;

class Cat {
    static String[] breeds;

    static {
        breeds = new String[]{"Сиамская", "Британская", "Мейн-кун"};
        Arrays.sort(breeds);
    }
}
Массив тут сначала создается, а потом сортируется, двумя действиями. Присваиванием при объявлении так не сделать, а в блоке можно. Существуют и другие способы инициализации полей в Java, но для начала достаточно и этих.

Вопросы и ответы

Какое значение у поля, если его не инициализировать?

Поле не остается пустым, ему автоматически присваивается значение по умолчанию. У всех ссылочных типов, включая String, это null. У byte, short, int и long это ноль, у float и double ноль с дробной частью, у char символ с нулевым кодом, у boolean false.

Чем инициализация при объявлении отличается от инициализации в конструкторе?

Значением, которое получат объекты. Если написать значение прямо в объявлении поля, оно будет одинаковым у всех создаваемых объектов. Конструктор с параметрами позволяет передать свои значения каждому объекту в момент создания.

Зачем в конструкторе пишут this.name = name?

Потому что имя параметра совпадает с именем поля. Без this компилятор поймет обе части как параметр, и присваивание получится бессмысленным: параметр присвоится сам себе, а поле останется со значением по умолчанию. Слово this явно указывает, что слева стоит поле текущего объекта.

Почему после добавления конструктора с параметрами перестал работать new Cat()?

Потому что конструктор без параметров создается автоматически только до тех пор, пока в классе не объявлен ни один свой конструктор. Как только вы написали конструктор с параметрами, бесплатный вариант пропадает. Если он нужен, его добавляют вручную, пустым телом.

Есть ли значения по умолчанию у локальных переменных?

Нет. Правило про значения по умолчанию действует только для полей класса, элементов массива и статических переменных. Локальную переменную внутри метода нужно явно проинициализировать до первого использования, иначе компилятор откажется собирать программу.

Можно ли задать значение поля после создания объекта?

Да, обычным присваиванием: cat1.name = "Том". Так делают, когда значение становится известно позже создания объекта. В реальном коде поля обычно закрывают модификатором private и меняют через методы-сеттеры, но механика та же.

Чем блок инициализации отличается от конструктора?

Блок инициализации это просто фигурные скобки в теле класса, у него нет ни имени, ни параметров, поэтому передать в него значения снаружи нельзя. Он выполняется при каждом создании объекта и всегда до тела конструктора. Смысл в том, чтобы не повторять общую подготовку в нескольких конструкторах сразу. Со словом static блок выполняется один раз, при загрузке класса, и работает со статическими полями.

Читайте также