Статический блок в Java не выполняется

87
class Test {
    public static void main(String arg[]) {    
        System.out.println("**MAIN METHOD");
        System.out.println(Mno.VAL); // SOP(9090);
        System.out.println(Mno.VAL + 100); // SOP(9190);
    }

}

class Mno {
    final static int VAL = 9090;
    static {
        System.out.println("**STATIC BLOCK OF Mno\t: " + VAL);
    }
}

Я знаю, что staticблок выполняется при загрузке класса. Но в этом случае переменная экземпляра внутри класса Mno- это finalиз-за того, что staticблок не выполняется.

Почему это так? И если бы я удалил final, все ли работало нормально?

Какая память будет выделена первой: static finalпеременная или staticблок?

Если из-за finalмодификатора доступа класс не загружается, то как переменная может получить память?

Стхита
источник
1
Какую точную ошибку и сообщение вы получите?
Паташу
@Patashu, ошибки нет, есть сомнения
Sthita

Ответы:

134
  1. static final intПоле является константой времени компиляции и его значение зашито в класс назначения без ссылки на его происхождение;
  2. поэтому ваш основной класс не запускает загрузку класса, содержащего поле;
  3. поэтому статический инициализатор в этом классе не выполняется.

В частности, скомпилированный байт-код соответствует этому:

public static void main(String arg[]){    
    System.out.println("**MAIN METHOD");
    System.out.println(9090)
    System.out.println(9190)
}

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

Марко Топольник
источник
1
Но как тогда оценивается значение последней переменной в классе без загрузки класса?
Сумит Десаи
18
Все вычисления происходят во время компиляции, и конечный результат жестко запрограммирован во всех местах, которые ссылаются на переменную.
Марко Топольник
1
Итак, если вместо примитивной переменной будет какой-то Object, то такое жесткое кодирование будет невозможно. Не так ли? Итак, в этом случае будет ли загружен этот класс и будет ли выполняться статический блок?
Сумит Десаи
2
Марко, сомнение Сумита верно и если вместо примитива это какой-то Object, то такое жесткое кодирование будет невозможно. Не так ли? Итак, в этом случае будет ли загружен этот класс и будет ли выполняться статический блок?
Стхита,
8
@SumitDesai Точно, это работает только для примитивных значений и строковых литералов. Полную информацию см. В соответствующей главе Спецификации языка Java
Марко Топольник,
8

Причина , почему класс не загружен в том , что VALэто final и он инициализирован с выражением постоянной (9090). Если и только если эти два условия выполняются, константа оценивается во время компиляции и «жестко запрограммирована» там, где это необходимо.

Чтобы выражение не оценивалось во время компиляции (и чтобы JVM загружала ваш класс), вы можете:

  • удалите последнее ключевое слово:

    static int VAL = 9090; //not a constant variable any more
    
  • или измените выражение в правой части на что-то непостоянное (даже если переменная все еще окончательная):

    final static int VAL = getInt(); //not a constant expression any more
    static int getInt() { return 9090; }
    
ассилий
источник
5

Если вы видите использование сгенерированного байт-кода javap -v Test.class, main () будет выглядеть так:

public static void main(java.lang.String[]) throws java.lang.Exception;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String **MAIN METHOD
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: sipush        9090
        14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: sipush        9190
        23: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        26: return        

Вы можете ясно видеть в " 11: sipush 9090", что статическое конечное значение используется напрямую, потому что Mno.VAL - это постоянная времени компиляции. Поэтому загружать класс Mno не требуется. Следовательно, статический блок Mno не выполняется.

Вы можете выполнить статический блок, вручную загрузив Mno, как показано ниже:

class Test{
    public static void main(String arg[]) throws Exception {
        System.out.println("**MAIN METHOD");
        Class.forName("Mno");                 // Load Mno
        System.out.println(Mno.VAL);
        System.out.println(Mno.VAL+100);
    }

}

class Mno{
    final static int VAL=9090;
    static{
        System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
    }
}
Xolve
источник
1
  1. На самом деле вы не расширяли этот класс Mno, поэтому при запуске компиляции он генерирует константу переменной VAL, а при запуске выполнения, когда эта переменная требуется, ее загрузка происходит из памяти. Таким образом, не требуется, чтобы ваша ссылка на класс, чтобы статический блок не выполнялся.

  2. если класс Aрасширяет класс Mno, статический блок включается в класс, Aесли вы это делаете, то этот статический блок выполняется. Например..

    public class A extends Mno {
    
        public static void main(String arg[]){    
            System.out.println("**MAIN METHOD");
            System.out.println(Mno.VAL);//SOP(9090);
            System.out.println(Mno.VAL+100);//SOP(9190);
        }
    
    }
    
    class Mno {
        final static int VAL=9090;
        static {
            System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
        }
    }
    
Ketan_Patel
источник
0

Насколько мне известно, он будет выполняться в порядке появления. Например :

 public class Statique {
     public static final String value1 = init1();

     static {
         System.out.println("trace middle");
     }
     public static final String value2 = init2();


     public static String init1() {
         System.out.println("trace init1");
         return "1";
     }
     public static String init2() {
         System.out.println("trace init2");
         return "2";
     }
 }

напечатает

  trace init1
  trace middle
  trace init2

Я только что протестировал его, и статика инициализируется (=> печать), когда класс «Statique» фактически используется и «выполняется» в другом фрагменте кода (в моем случае я использовал «new Statique ()».

Fabyen
источник
2
Вы получаете этот результат, потому что загружаете Statiqueкласс, выполняя new Statique(). Хотя в заданном вопросе Mnoкласс вообще не загружается.
РАН
@Fabyen, если я создаю объект Mno в тестовом классе следующим образом: Mno anc = New Mno (); тогда все в порядке, но в текущем сценарии я этого не делаю, я сомневаюсь, что если я удаляю final, тогда статический блок выполняется нормально, иначе он не выполняется, почему так ??
Стита
1
Да, ответ ниже идеален. В байт-коде Main.class (с использованием Mno.VAL) 9090 жестко запрограммирован. Удалите final, скомпилируйте, затем используйте javap Main, вы увидите getstatic # 16; // Поле Statique.VAL: I . Верните финальную версию, скомпилируйте, затем используйте javap Main, вы увидите sipush 9090 .
Fabyen
1
Поскольку он жестко запрограммирован в Main.class, нет причин загружать класс MNO, следовательно, нет статической инициализации.
Fabyen
Это отвечает на второй вопрос: «Какая память будет выделена первой: статическая конечная переменная или статический блок?» (лексический порядок)
Хауке Ингмар Шмидт