Значение статической переменной не изменилось даже после инициализации дочернего класса в Java

Когда я Checks.y статическую переменную y с помощью Checks.y (Checks является подклассом), статический блок не выполняется и значение y не обновляется.

class Par {
    static int y = 4;
}

class Checks extends Par {
    static {
        y = 5;
    }
}

public class Check {
    public static void main(String args[]) {
        System.out.println(Checks.y); // here printing 4
    }
}

Поскольку static является общим для всех подклассов, предполагается, что значение будет обновлено.

Что может быть причиной этого?

Ответ 1

Поле y не объявлено классом Checks.

Чтение статических полей не запускает инициализацию ссылочного класса (Checks), если только этот класс не является тем, в котором объявлено поле (см. Цитату JLS ниже). В этом примере, даже если к y обращаются через Checks, это вызовет только инициализацию Par потому что Par является классом, объявляющим y.

Другими словами, класс Checks в некотором смысле не используется во время выполнения.

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


В спецификации есть простое объяснение:

12.4.1. Когда происходит инициализация

Класс T или интерфейсный тип T будут инициализированы непосредственно перед первым появлением любого из следующего:

  • T является классом, и экземпляр T создан.

  • Статический метод, объявленный T, вызывается.

  • Статическое поле, объявленное T, присваивается.

  • Используется статическое поле, объявленное T, и поле не является константной переменной (§4.12.4).

  • T является классом верхнего уровня (§7.6), и выполняется утверждение assert (§14.10), лексически вложенное в T (§8.1.3).
    ...
    Ссылка на статическое поле (§8.3.1.1) вызывает инициализацию только того класса или интерфейса, который фактически объявляет его, даже если на него можно ссылаться через имя подкласса, подынтерфейса или класса, который реализует интерфейс.

Последнее примечание объясняет, почему ваш подкласс не инициализируется.

Ответ 2

Из JLS 12.4.1:

Класс T или интерфейсный тип T будут инициализированы непосредственно перед первым появлением любого из следующего:

  • T является классом, и экземпляр T создан.
  • T является классом, и вызывается статический метод, объявленный T.
  • Статическое поле, объявленное T, присваивается.
  • Используется статическое поле, объявленное T, и поле не является константной переменной (§4.12.4).
  • T является классом верхнего уровня (§7.6), и выполняется утверждение assert (§14.10), лексически вложенное в T (§8.1.3).

Поскольку у не объявлен в проверках, ни один из вышеуказанных критериев не выполняется.

Еще один способ проиллюстрировать это поведение:

class par {
    static int y = 4;
    static {
        System.out.println("static constructor of par");
    }
}

class checks extends par {
    static int x = 6;
    static {
        System.out.println("checks static constructor");
        y = 5;
    }
}

public class check{
    public static void main(String args[]){
        System.out.println(checks.y);
        System.out.println(checks.x);
        System.out.println(checks.y);
    }
}

Выход

static constructor of par
4
checks static constructor
6
5

Таким образом, после вызова checks.x который удовлетворяет второму правилу, вызывается статический конструктор.

Ответ 3

Это потому, что static блок в классе checks не выполняется. Хотя вы упоминаете checks классов, JVM не загружает их вообще.

Вы можете подтвердить это, добавив еще один System.out.println внутри статического блока.

class checks extends par {

    static {
        System.out.println("Test");
        y = 5;
    }
}

Слово " Test никогда не будет напечатано.


Прочтите раздел 12.4.1. Когда Инициализация Происходит Спецификации языка Java, чтобы знать больше.

Ответ 4

Вот:

System.out.println(checks.y); // Here printing 4

y относится к полю класса par. Это поле доступа приводит к загрузке класса par (родительского класса) в соответствии с JLS (выделено мое):

12.4. Инициализация классов и интерфейсов

....

12.4.1. Когда происходит инициализация

Класс T или интерфейсный тип T будут инициализированы непосредственно перед первым появлением любого из следующего: T является классом, и создается экземпляр T. Статический метод, объявленный T, вызывается.

Статическое поле, объявленное T, присваивается.

Используется статическое поле, объявленное T, и поле не является константной переменной (§4.12.4).

T является классом верхнего уровня (§7.6), и выполняется утверждение assert (§14.10), лексически вложенное в T (§8.1.3).

Но он не загружает класс checks потому что (акцент мой):

Ссылка на статическое поле (§8.3.1.1) вызывает инициализацию только того класса или интерфейса, который фактически объявляет его, даже если на него можно ссылаться через имя подкласса, подынтерфейса или класса, который реализует интерфейс.

Ответ 5

Один аспект, не упомянутый до сих пор, может сбить с толку новых Java-программистов: факт организации вашего исходного кода не имеет значения в определенной степени!

У вас есть два класса (которые должны называться Parent и Child, кстати, в соответствии с соглашениями об именах Java) в одном файле или в одном примере. Таким образом, вы, вероятно, предполагаете: эти вещи собираются автоматически во время выполнения.

Но во время выполнения существуют отдельные файлы классов для каждого класса. И, как сказали другие: ничего в коде не ссылается на дочерний класс. Таким образом, этот класс не загружен, поэтому назначение не происходит!

Ответ 6

Как уже упоминалось, статический блок не выполняется, потому что класс не инициализирован, как было сказано ранее.

Обратите внимание, что имена соглашений классов начинаются с заглавной буквы.

Более ясный пример, показывающий, что класс Overriding не используется:

class Par {
    static int y = 4;
    public static void main(String args[]) {
        System.out.println(Checks.y);    // Here printing 4
        System.out.println(new Checks().y);    // Here printing 5
    }
}

class Checks extends Par {
   static {
        y = 5;
    }
}

Ответ 7

Согласно вашему примеру, статический блок класса Check никогда не вызывается. Статические блоки всегда запускаются до создания объекта. Если вы добавите проверки, object = new checks(), в вашем классе Check вы должны увидеть ожидаемое значение.

Ответ 8

Вот вариант принудительной инициализации класса Checks.

class Par {
    static int y = 4;
}

class Checks extends Par {
    public static int x;
    static {
        y = 5;
    }
}

 class Check {
    public static void main(String args[]) {
        System.out.println(checks.y); // Prints 4
        System.out.println(checks.x); // Prints 0
        System.out.println(checks.y); // Prints 5
    }
}