2016-03-13 3 views
4

Можно ли вызвать статический метод из статического инициализатора в Java? Является ли следующее действующим и гарантированным работать в соответствии со спецификацией Java?Может ли статический инициализатор Java вызвать статический метод?

public class Foo { 

    private final static int bar; 

    private static int generateValue() { 
    return 123; 
    } 

    static { 
    bar = generateValue(); 
    } 

} 

Что заставляет меня задаться вопросом, что я мог бы ожидать bar быть доступны внутри generateValue(). Я знаю, что порядок статических блоков инициализатора важен, но я не слышал о том, что порядок объявления статических методов является значительным. Но доступны ли статические методы перед выполнением статических блоков инициализатора?

+4

** Да **. Я не знаю, что это ** гарантировано ** для работы, ваш код должен быть дефектным; и JVM должен быть неисправен (и ваша операционная система и компьютер). Было бы также ** законным ** писать 'private final static int bar = generateValue();' –

ответ

3

Одним словом - да. Это совершенно легальный код и должен [статически] инициализировать bar до 123.

+0

Я предполагаю, что generateValue(), как предполагается, возвращает что-то законное, а не жестко закодированное. –

1

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

private final static int bar = generateValue(); 

даже если метод generateValue() определяется после статического члена (и я просто попытался это, чтобы убедиться).

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

private final InetAddress inet = InetAddress.getByName ("some bad host name"); 

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

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

3

Как @Mureinik сказал: «Одним словом, этот код совершенно легален». Я хочу дать более подробный ответ, потому что вы можете легко создавать проблемы при использовании статических инициализаторов в сочетании с методами класса - порядок появления может повлиять на состояние класса, и это неприятная ошибка для отслеживания.

статический инициализатор объявлены в классе выполняется при инициализации класса. Вместе с любыми полевыми инициализаторами для переменного класса ... статические инициализаторы могут быть использованы для инициализации класса переменного класса - Java Language Specification (JLS) §8.7

Инициализация обычно происходит в порядке появления (так называемый текстуального заказ). Например, рассмотрим следующий код:

class Bar { 
    static int i = 1; 
    static {i += 1;} 
    static int j = i; 
} 

class Foo { 
    static int i = 1; 
    static int j = i; 
    static {i += 1;} 

    public static void main(String[] args) { 
     System.out.println("Foo.j = " + Foo.j); 
     System.out.println("Bar.j = " + Bar.j); 
    } 
} 

Значение Foo.j и Bar.j отличаются из-за различий в текстологии упорядочения кода:

Foo.j = 1 
Bar.j = 2 

пример Ор в выполняется в текстовом порядке. Но что, если код был перестроен, скажем, в обратном порядке:

class Foo { 
    static { bar = generateValue(); }     //originally 3rd 
    private static int generateValue() { return 123; } //originally 2nd 
    private final static int bar;      //originally 1st 

    public static void main(String[] args) { 
     System.out.println("Foo.bar = " + Foo.bar); 
    } 
} 

Оказалось, что это компилируется без ошибок.Кроме того, выход: Foo.bar = 123. Таким образом, bar действительно содержит 123 во время выполнения. Однако, следующий код (от JLS §8.3.1.1) производит компиляцию ошибки времени, потому что он пытается получить доступ к j до того j объявлено:

//Don't do this! 
class Z { 
    static { i = j + 2; } //Produces a compilation error 
    static int i, j; 
    static { j = 4; } 
} 

Интересно, что доступ методов не проверяется таким образом, так:

class Foo { 
    static int peek() { return j; } 
    static int i = peek(); 
    static int j = 1; 

    public static void main(String[] args) { 
     System.out.println("Foo.i = " + Foo.i); 
    } 
} 

производит следующий вывод:

Foo.i = 0 

это происходит потому, что

переменный инициализатор i использует метод класса peek для доступа к значению переменной j перед j инициализирован ее инициализатором переменным, в этот момент он по-прежнему имеет значение по умолчанию - JLS §8.3.2.3

Если вместо этого, i инициализируется послеj, то выход

Foo.i = 1 

И ситуация становится хуже при использовании объектов вместо примитивных типов, как:

class Foo { //Don't do this 
    static int peek() { return j.hashCode(); } // NullPointerException here 
    static int i = peek(); 
    static Object j = new Object(); 

    public static void main(String[] args) { 
     System.out.println("Foo.i = " + Foo.i); 
    } 
} 

Это peek бросает NullPointerException при инициализации i:

Exception in thread "main" java.lang.ExceptionInInitializerError 
Caused by: java.lang.NullPointerException 
    at TestGame.Foo.peek(Foo.java:4) 
    at TestGame.Foo.<clinit>(Foo.java:5) 

Eclipse, всплывает окно, когда выше код выполняется:

Java Exception has occurred

Привязав это обратно к OP, если вместо возврата 123 метод generateValue() вернул значение некоторого другого статического поля (или метода), то значение bar зависит от текстового упорядочения кода.

Итак, когда текстовое упорядочение важно?

Текстурный заказ не всегда используется. Иногда JVM ожидает выполнения инициализации. Важно знать, когда возможен внешний вид, и когда инициализация происходит в текстовом порядке. JLS описывает Restrictions on the use of Fields during Initialization in §8.3.2.3 (курсив мой собственный):

Заявление члена должно появиться текстуально, прежде чем она используется только если член [а] ... статическое поле класса или интерфейса C и все из следующих условий:

  • использования происходит в [а] ... статический переменный инициализатор с или в [а] ... статический инициализатор С.
  • использование не в левой части задания.
  • Использование осуществляется по простому имени.
  • C является самым внутренним классом или интерфейсом, охватывающим использование.

последнего замечание: константы инициализируются первым (на JLS §8.3.2.1):

Во время выполнения статических полей, которые являются окончательными и инициализируются с постоянными выражениями (§15.28) сначала инициализируются (§12.4.2).