Подтвердить что ты не робот

Статический окончательный int v/s static int

Эта проблема была взята из моего учебного пособия Java test. Может ли кто-нибудь объяснить, почему так оно и есть?

Этот код печатает номер 5, а не 12. Не могли бы вы объяснить, почему? Не могли бы вы объяснить, почему он будет печатать 12, если вторая переменная была также final, а 0, если ни одна из них не была final?

public class Question26 {
    public static void main(String[] args) {
        System.out.println(Q26.q26.ans);
    }
}

class Q26 {
    public static Q26 q26 = new Q26();
    public int ans;
    private static final int var1 = 5;
    private static int var2 = 7;

    public Q26() {
        ans = var1 + var2;
    }
}
4b9b3361

Ответ 1

Одна вещь, чтобы знать, где вы объявляете поля static, состоит в том, что они инициализируются по порядку; вы не можете написать:

public class DoesNotCompile
{
    private static final int foo = 1 + bar; // ERROR: bar is not defined
    private static final int bar = 1;

В вашей ситуации, однако, все немного отличается:

class Q26 {
    // Declared first, but NOT first to be initialized...
    public static Q26 q26 = new Q26();
    public int ans;
    // The honor befalls to this one, since it is declared `final`
    private static final int var1 = 5;
    private static int var2 = 7; // zero until initialized

    public Q26() {
        ans = var1 + var2;
    }
}

Значение по умолчанию для не инициализированного int равно 0; поскольку ваш экземпляр Q26 объявлен до var1 и var2, , но, поскольку сначала инициализируется var1 (так как это final), результат - то, что вы видите: ans 5.

Эквивалент этого кода может быть:

class Q26 {
    public static Q26 q26;
    private static final int var1;
    private static int var2;

    static {
        var1 = 5;
        q26 = new Q26();
        var2 = 7;
    }

    public int ans;

    public Q26() {
        ans = var1 + var2;
    }
}

Кроме того, есть также статические блоки инициализации; и порядок имеет значение для них. Вы не можете:

public class DoesNotCompileEither
{
    static {
        foo = 3; // ERROR: what is foo?
    }
    private static final int foo;

Если статический инициализатор ставится ниже декларации foo, то это скомпилирует:

public class ThisOneCompiles
{
    private static final int foo; // declared
    static {
        foo = 3; // initialized
    }

Ответ 2

Статические члены вашего класса не все магически инициализируются одновременно. Под капотом Java должен их установить - и он делает это в том порядке, в котором вы их объявили, за исключением final.

Позволяет перезаписать, поэтому проблема становится понятной:

public class Question26 {
    public static void main(String[] args) {
        System.out.println(Q26.q26.ans);
    }
}
class Q26 {
    public static Q26 q26;
    public int ans;
    private static final int var1 = 5;
    private static int var2;

    static {
        q26 = new Q26();
        var2 = 7;
    }

    public Q26() {
        ans = var1 + var2;
    }
}

Здесь объявление членов не final static и их инициализация разделены. Ввод инициализации в статическом блоке делает несколько более очевидным, что инициализация этих членов является кодом, как и любой другой, и имеет порядок выполнения.

Но почему переменная final инициализирована до q26? Глядя на этот ответ и на спецификацию Java, кажется, что var1 может рассматриваться как выражение константы времени компиляции. Даже если он не обрабатывается как таковой, хотя, если он объявлен и инициализирован в одном из операторов, var1 должен быть инициализирован средой выполнения перед переменными final:

Во время выполнения сначала инициализируются статические поля, которые являются окончательными и которые инициализируются постоянными выражениями. Это также относится к таким полям в интерфейсах. Эти поля являются "константами", которые никогда не будут наблюдаться, когда их начальные значения по умолчанию даже при использовании коварных программ.

Образец цитирования: http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.2.1

Так что, если мы сходим с ума, разделив объявление и инициализацию члена static final var1? В этом случае мы могли бы скомпилировать и выполнить вашу программу только для вывода 0 - что противоречит некоторым утверждениям, которые вы сделали в своем вопросе.

public class Question26 {
    public static void main(String[] args) {
        System.out.println(Q26.q26.ans);
    }
}
class Q26 {
    public static Q26 q26;
    public int ans;
    private static final int var1;
    private static int var2;

    static {
        q26 = new Q26();
        var1 = 5;
        var2 = 7;
    }

    public Q26() {
        ans = var1 + var2;
    }
}

Так что не обманывайте себя, думая, что ключевые слова, которые вы используете для объявления переменных, обеспечивают определенный порядок выполнения!

Ответ 3

Это из-за порядка инициализации статических членов, они будут инициализированы текстовым порядком, который они объявили. Определите переменные int var1 и var2 перед этой переменной объекта q26, как показано ниже

class Q26 {
    private static final int var1 = 5;
    private static int var2 = 7;
    public static Q26 q26 = new Q26();
    public int ans;


    public Q26() {
        ans = var1 + var2;
    }
}

Теперь выход 12

В вашем случае объект q26 был инициализирован перед переменной var2, но var1 является окончательным vaiable, значение известно во время компиляции. Итак, вы получили этот ответ

Ответ 4

Из http://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html

Если примитивный тип или строка определяется как константа, а значение известно во время компиляции, компилятор заменяет константу имя везде в коде с его значением. Это называется константа компиляции. Если значение константы во внешнем мировые изменения (например, если принято законодательство о том, что pi фактически должно быть 3.975), вам нужно будет перекомпилировать любые классы, которые используют это константа, чтобы получить текущее значение.

Поэтому, несмотря на то, что эта переменная определена после статического q26, компилятор заменил каждое место var1 на 5.