Задачка из OCA по Java
Упрощенный вид одной из задачи: Что будет выведено на экран?
public class NewClass {
static int i;
public static void main(String[] args) {
System.out.println(new NewClass().method());
}
public int method(){
int a;
if(i < 10) a = 5;
return a;
}
}
Как я сказал выше - задача упрощенная, но смысл в ней тот же. В исходной задаче для размыливания взгляда в метод передаются различные параметры и прочее.
Конечно же этот код не компилируется. "Есть не нулевая вероятность того, что статическая переменная i будет больше 10 и тогда локальная переменная "а" никогда не будет проиницилизирована". Естественно нам то понятно, что i всегда будет 0. Но а вдруг другой поток захочет ее изменить? Как можно решить эту задачу? Правильно, сделать i финальной. Давайте сделаем.
public class NewClass {
final static int i;
static{
i = 0;
}
public static void main(String[] args) {
System.out.println(new NewClass().method());
}
public int method(){
int a;
if(i < 10) a = 5;
return a;
}
}
Бам! Код все равно не компилируется! Хотя, если проиницилизровать финальную переменную в строке ее объявления всё проходит. Вот и прошу объяснить этот момент. Почему код не компилируется.
Объясню свою логику и прошу поправить если не прав: для вызова метода method() нам нужен инстанс класса. По спецификации Java (12.4.1), класс будет загружен в память в одном из 5 случаев (один из случаев - создание объекта). Т.е. статический блок выполнится в 100% случаев и соответственно будет проиницилизирована переменная i. Но как было сказано выше, код не компилируется. И я где-то не прав... Прошу знатоков объяснить!
Ответы (1 шт):
Хоть вопрос был задан давно, да и вы сами, в принципе, на него ответили, хотелось бы привести некоторые последовательные и несложные рассуждения.
Чтобы блок if выполнился и переменная a была проинициализирована, мы должны гарантировать, что к моменту вызова метода method() статическая переменная i будет иметь значение меньше 10-ти.
В случае
static int i;
этого не достигается. Ведь хоть при загрузке класса в память переменная i и инициализируется нулём, но не факт, что до вызова метода method() не вызовется какой-нибудь другой метод, который может изменить i, сделав её, например, равной 11.
Чтобы гарантировать неизменяемость i, мы можем сделать её final. Однако полагаясь на её инициализацию в статическом блоке
static final int i;
static {
i = 0;
}
ситуацию мы не улучшаем, ведь, как вы правильно сказали в комментариях, в статических блоках инициализации может быть сделано всё что угодно. В том числе может быть вызван наш метод method(), в то время как i пока что, опять же, будет не инициализирована.
И наконец запись
static final int i = 0;
позволяет коду запуститься. Мы сразу же на месте присваиваем i значение, и больше она никоим образом измениться не может. Поэтому всяких теоретически небезопасных случаев развития кода не будет и код сможет запуститься.
Если говорить правильнее, то static final-переменные (проинициализированные сразу, а не в статическом блоке) - это константные выражения времени компиляции. Если из другого класса обращаться к таким переменным, то загрузка класса, содержащего их, в память осуществляться вообще не будет. Такое явление было разобрано в этом вопросе.