Задачка из 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 шт):

Автор решения: Byb

Хоть вопрос был задан давно, да и вы сами, в принципе, на него ответили, хотелось бы привести некоторые последовательные и несложные рассуждения.

Чтобы блок 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-переменные (проинициализированные сразу, а не в статическом блоке) - это константные выражения времени компиляции. Если из другого класса обращаться к таким переменным, то загрузка класса, содержащего их, в память осуществляться вообще не будет. Такое явление было разобрано в этом вопросе.

→ Ссылка