Поля класса. Ключевое слово static. Константы
ВАЖНО: ниже находится устаревшая версия статьи. Актуальную можно найти здесь: ссылка.
Поля класса
Мы уже познакомились с таким синтаксисом как поле. Оно позволяет хранить определенную информацию внутри объекта, являясь, по сути, переменной уровня класса. Сегодня мы более подробно разберемся с особенностями этого синтаксиса.
Первое, что хотелось бы отметить – дефолтные (по умолчанию) значения полей. Если обычные переменные мы обязаны инициализировать явно (некоторые из вас уже столкнулись с ошибкой компиляции, если этого не сделать), то с полями класса все иначе.
Если мы создали поле класса и ни на каком этапе не задаем значение этого поля, Java не увидит в этом ошибки. По той простой причине, что она неявно дала значение каждому полю сразу при его объявлении, в зависимости от его типа:
· byte, short, int, long – инициализируются значением 0;
· char – инициализируется символом, код которого – 0. Данный символ нечитаем, но он есть. Вы можете прочесть больше, загуглив «\u0000»;
· double, float – значением по умолчанию будет 0.0;
· boolean – будет инициализирован значение false;
· Абсолютно все ссылочные типы, включая хорошо известный нам String – инициализируются значением null.
Если с примитивами все просто, то с null мы ранее не сталкивались. Это ключевое слово, которое означает, что переменная ссылочного типа не содержит никакой ссылки на объект. Соответственно, обращение к полям и методам такой переменной завершится с ошибкой (исключением). Поэтому при работе с ссылочными типами достаточно часто используется проверка на null вида (object != null).
Также стоит рассмотреть способы инициализации полей класса. Мы уже знакомы с инициализацией с помощью конструктора, также в статье на metanit было упоминание о блоках инициализации (они нас не интересуют, поскольку данный функционал де-факто не используется).
Кроме этого, инициализацию полей мы можем производить в методах класса, если того требует логика нашего класса или приложения. Первичная инициализация в методе – достаточно узкий случай, а вот изменять значения в методе мы даже попробовали самостоятельно в практическом задании прошлого урока.
И последним, хоть и самым очевидным, способом первичной инициализации является инициализация при объявлении. Ровно также, как мы часто делаем с переменными. Инициализировать таким образом мы можем поля и примитивных, и ссылочных типов:
public class Car {
public int maxSpeed = 240;
public String color = "Красный";
public Counter mileage = new Counter("Пробег");
}
Кроме того, мы можем использовать одни поля класса при инициализации других. Главное, чтобы инициализируемое поле было расположено ниже тех, которые используются для инициализации:
public class FullName {
public String firstName = "Иван";
public String lastName;
public String fullName = firstName + " " + lastName;
}
При этом нет разницы, инициализированы использованные поля явно или нет. В нашем примере поле fullName будет инициализировано значением "Иван null".
В любом случае, я не советую использовать инициализацию поля при объявлении, по крайней мере, на данном этапе. Зачастую такая логика приводит к излишней запутанности и неожиданным багам. Исключением являются константы, но об этом ниже.
Ключевое слово static
Мы уже встречались со static’ом ранее, при знакомстве с методами, но использовали его без понимания того, что он делает и для чего нужен.
Данное ключевое слово можно применить к полю и методу (еще к вложенному классу, но не трогайте, это на Новый Год).
В обоих случаях данное ключевое слово будет означать, что поле/метод относится не к объекту класса, а непосредственно к классу. Т.е. нам не нужно создавать объект, чтобы использовать данное поле/метод.
Ярким примером static-поля может быть поле out класса System: System.out – мы обращаемся к нему, не создавая объект класса System. Общий синтаксис обращения к статическому полю: имя_класса.имя_поля.
Наиболее распространенное применение статических полей – константы. В следующем подразделе мы разберем их подробнее.
Статические методы, в свою очередь, хорошо демонстрирует уже известный нам класс Math. Например: Math.pow(2, 3). Здесь мы вызываем метод, не создавая объект класса Math. Общий синстаксис: имя_класса.имя_метода([аргументы метода]).
Наиболее распространенное (и, практически, единственное) применение static-методов – утилитарные методы, в которых объект не нужен. Например, математические операции, простейшие конвертеры и пр.
На самом деле, тема использования static-методов намного глубже, чем кажется на первый взгляд и допустимость их использования тесно связано как с концепцией ООП, так и с особенностями реализации этой концепции в Java. Поэтому если с static-полями мы почти полностью разберемся уже сегодня, то к static-методам еще будем возвращаться несколько раз.
Немного о нюансах использования. С методами это не так критично (разберем чуть ниже), но с полями неграмотное использование static может сыграть злую шутку. Рассмотрим на примере:
public class Counter {
public static int counter;
}
public static void main(String… args) {
Counter counter1 = new Counter();
Counter counter2 = new Counter();
counter1.counter++;
counter2.counter++;
System.out.println(counter1.counter + " " + counter2.counter);
}
Вывод на консоль:
2 2
Поскольку static-поля относятся именно к классу, а не объекту, значение такого поля будет общим на весь класс. По сути, код выше равноценен следующему:
public class Counter {
public static int counter;
}
public static void main(String… args) {
Counter.counter++;
Counter.counter++;
System.out.println(Counter.counter + " " Counter.counter);
}
Использование static-методов имеет свои ограничения: мы не можем внутри статического метода обратиться к не статическому полю/методу, не создав экземпляр класса (использовать this – тоже не можем). Т.е:
public class SthClass {
public static void doSth() {
doSth1();
}
public void doSth1() {
//Какая-то логика
}
}
Приведет к ошибке компиляции. Чтобы подобный код заработал, нужно сделать следующее:
public class SthClass {
public static void doSth() {
SthClass sthObject = new SthClass();
sthObject.doSth1();
}
public void doSth1() {
//Какая-то логика
}
}
К слову, именно поэтому при первом знакомстве с методами я рекомендовал помечать их как static – чтобы мы могли их использовать в статическом методе main() до того, как изучим конструкторы.
Однако если вы в рамках статического метода создаете объект того же класса – почти гарантированно вы где-то свернули не туда. Исключения есть, но к моменту, когда они вас коснутся, использование static вряд ли будет вызывать у вас какие-либо вопросы.
Константы
Мы уже знакомы с тем, как объявить переменную-константу внутри метода. Константы уровня класса достаточно похожи, для их объявления также используется ключевое слово final. Но есть и отличия.
Первое из них заключается в том, что инициализировать константное поле мы можем не только при объявлении, но также и в конструкторе, и в, упаси Господи, в блоке инициализации. Но для одной константы может быть использован только один способ инициализации. Не инициализировать константу вовсе – нельзя, это будет ошибкой компиляции.
Однако под константными полями обычно подразумеваются не просто final-поля (в каком-то смысле, их можно назвать константами уровня объекта и они тоже имеют право на жизнь), а поля, помеченные как static final – константы уровня класса.
В такие поля часто выносят литералы, которые не будут изменяться в ходе программы. Это могут быть какие-то строки, особенно, если одно строковое значение используется в рамках класса (или приложения) несколько раз, числа, особенно, если это какие-либо коэффициенты для расчетов и т.д.
Примером такой константы может быть
public static final double PI = 3.14159265358979323846;
в классе Math. Обратите внимание на нейминг: константы называют прописными буквами, слова разделяются символом ' _'. Например: SOMETHING_CONSTANT. Это сделано для того, чтобы отличать константные поля от обычных.
Если мы вспомним Задачу 3 (рисование прямоугольника) из урока про циклы, в константы стоило бы вынести ' -', '|', ' '. Ведь если мы захотим нарисовать прямоугольник другими символами, заменить их будет проще в одном месте, чем искать по коду, особенно, если он разделен на методы.
Обращение к константе уровня класса ничем не отличается от обращения к обычному статическому полю. Кроме того, что изменить значение такого поля мы не сможем.
К слову, final-методы тоже существуют, но в них смысл слова final уже иной. Мы можем даже создавать final-static-методы. Другой вопрос, что такое объявление избыточно, почему – разберем в уроке, посвященном наследованию в Java.
С теорией на сегодня все!

Переходим к практике:
Задача 1:
Используя кодовую базу из задачи https://github.com/KFalcon2022/practical-tasks/blob/master/src/com/walking/lesson6_methods/Task3.java вынести строковые и символьные литералы в константы. Попробуйте нарисовать прямоугольник, используя "==" для каждой единицы длины и "||" – для каждой единицы ширины.
Также попробуйте записать в константу переменную scanner. Упростится ли использование сканера внутри методов чтения с клавиатуры?
Задача 2:
Для задачи https://github.com/KFalcon2022/practical-tasks/tree/master/src/com/walking/lesson8_classes_objects реализуйте неизменность поля названия у класса Counter. Ведь очень странно, если мы можем менять название счетчика по ходу выполнения программы, не так ли?
Задача 3:
Используя задачу https://github.com/KFalcon2022/practical-tasks/blob/master/src/com/walking/lesson7_varargs_overloading/Task5.java (можете сделать на основе своего решения, но для наглядности удобства новых возможностей рекомендую взять за основу решение по ссылке):
1. Вынесите поиск простых чисел в отдельный класс.
2. Реализуйте возможность вывода на экран суммы N первых простых чисел, где N – число, введенное пользователем с клавиатуры;
3. Вынесите нужные вам переменные в поля класса. Если необходимо – сделайте их константами уровня класса или объекта. Помните, константа ссылочного типа гарантирует неизменность ссылки, а не содержимого объекта. Массив – ссылочный тип.
Примечание: это одна задача, а не различные варианты:)
Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)
Канал: https://t.me/+relA0-qlUYAxZjI6
Мой тг: https://t.me/ironicMotherfucker
Дорогу осилит идущий!