Статичний блок у 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, помилки немає, це сумнів
Стіта

Відповіді:


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
Отже, якщо замість примітивної змінної це якийсь Об’єкт, то таке жорстке кодування буде неможливим. Чи не так? Отже, у такому випадку, чи буде цей клас завантажений і статичний блок буде виконаний?
Суміт Десай

2
Марко, сумнів Суміта справедливий, якщо замість примітивного це якийсь Об'єкт, то таке жорстке кодування буде неможливим. Чи не так? Отже, у такому випадку, чи буде цей клас завантажений і статичний блок буде виконаний?
Стіта

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);
    }
}

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);
        }
    }
    

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

Я просто перевірив його, і статика ініціалізується (=> print), коли клас "Statique" фактично використовується і "виконується" в іншому фрагменті коду (у моєму випадку я зробив "new Statique ()".


2
Ви отримуєте цей результат, оскільки завантажуєте Statiqueклас, виконуючи new Statique(). Поки що в запитаному запитанні, Mnoклас взагалі не завантажується.
RAS

@Fabyen, якщо я створюю об'єкт Mno в тестовому класі, як це: Mno anc = New Mno (); тоді це нормально, але за поточним сценарієм я цього не роблю, я сумніваюся, що якщо я видаляю фінал, то статичний блок виконується нормально, інакше він не виконується, чому так ??
Стіта

1
Так, відповідь нижче ідеальна. У байт-коді Main.class (з використанням Mno.VAL) 9090 знаходиться в жорсткому кодуванні. Видаліть final, скомпілюйте, а потім використовуйте javap Main, ви побачите getstatic # 16; // Поле Statique.VAL: I . Покладіть назад final, скомпілюйте, а потім використовуйте javap Main, ви побачите sipush 9090 .
Fabyen

1
Оскільки це жорстко закодовано в Main.class, немає причин завантажувати клас MNO, отже, немає статичної ініціалізації.
Fabyen

Це відповідає на друге запитання: "Яка пам'ять буде виділена першою, статична кінцева змінна або статичний блок?" (лексичний порядок)
Hauke ​​Ingmar Schmidt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.