Почему мы не можем использовать неинициализированную локальную переменную для доступа к статическому контенту из типа этой переменной?
При попытке получить доступ к статическому полю x
через неинициализированную локальную переменную Foo foo;
foo.x
я получаю ошибку компиляции Variable 'foo' might not have been initialized
.
class Foo{
public static int x = 1;
public static void main(String[] args) {
Foo foo;
System.out.println(foo.x); // Error: Variable 'foo' might not have been initialized
}
}
Может показаться, что эта ошибка имеет смысл, но только до тех пор, пока мы не поймем, что для доступа к члену static
компилятор фактически не использует значение переменной, а только ее тип type.
Например, я могу инициализировать foo
значением null
, и это позволит нам получить доступ к x
без проблем:
Foo foo = null;
System.out.println(foo.x); //compiles and while running prints 1!!!
Такой сценарий работает, потому что компилятор понимает, что x
является статическим, и обрабатывает foo.x
так, как он был написан как Foo.x
(по крайней мере, я так думал до сих пор).
Так почему же компилятор вдруг настаивает на том, чтобы foo
имел значение, которое он НЕ будет использовать в этом месте?
(Disclaimer: This is not code which would be used in real application, just interesting phenomenon which I couldn't find answer to on Qaru so I decided to ask about it.)
Ответы
Ответ 1
§15.11. Выражения доступа к полю:
Если поле статическое:
Первичное выражение оценивается, а результат отбрасывается. Если оценка первичного выражения завершается преждевременно, выражение доступа к полю завершается преждевременно по той же причине.
Там, где раньше говорится, что доступ к полю определяется Primary.Identifier
.
Это показывает, что, хотя кажется, что он не использует Primary
, он все равно оценивается, а затем результат отбрасывается, поэтому его необходимо инициализировать. Это может иметь значение, когда оценка останавливает доступ, как указано в цитате.
EDIT:
Вот короткий пример, чтобы наглядно продемонстрировать, что Primary
оценивается, даже если результат отбрасывается:
class Foo {
public static int x = 1;
public static Foo dummyFoo() throws InterruptedException {
Thread.sleep(5000);
return null;
}
public static void main(String[] args) throws InterruptedException {
System.out.println(dummyFoo().x);
System.out.println(Foo.x);
}
}
Здесь вы можете видеть, что dummyFoo()
все еще оценивается, потому что print
задерживается на 5 секунд Thread.sleep()
, даже если он всегда возвращает значение null
, которое отбрасывается.
Если выражение не было оценено, print
появилось бы мгновенно, что можно увидеть, когда класс Foo
используется напрямую для доступа к x
с помощью Foo.x
.
Примечание: вызов метода также считается Primary
, показанным в §15.8 Первичные выражения.
Ответ 2
Каждая локальная переменная (§14.4) и каждое пустое конечное поле (§4.12.4, §8.3.1.2) должны иметь определенно назначенное значение при любом доступе к его значению.
Неважно, к чему вы пытаетесь получить доступ через локальную переменную. Правило таково, что его надо обязательно назначить до этого.
Чтобы оценить выражение доступа к полю foo.x
, его часть primary
(foo
) должна быть оценена первой. Это означает, что доступ к foo
будет иметь место, что приведет к ошибке во время компиляции.
Для каждого доступа к локальной переменной или пустому конечному полю x, x обязательно должен быть назначен до доступа, или во время компиляции.
Ответ 3
Есть смысл в том, чтобы правила были настолько простыми, насколько это возможно, и "не используйте переменную, которая не могла быть инициализирована" настолько просто, насколько это возможно.
Более того, существует установленный способ вызова статических методов - всегда используйте имя класса, а не переменную.
System.out.println(Foo.x);
Переменная 'foo' - это нежелательная служебная информация, которая должна быть удалена, и ошибки и предупреждения компилятора можно рассматривать как помощь, ведущую к этому.
Ответ 4
Другие ответы прекрасно объясняют механизм, который стоит за происходящим. Может быть, вы также хотели обоснование спецификации Java. Не будучи экспертом по Java, я не могу дать вам первоначальные причины, но позвольте мне указать на это:
- Каждый фрагмент кода либо имеет значение, либо вызывает ошибку компиляции.
- (Для статики, поскольку экземпляр не нужен,
Foo.x
является естественным.)
- Теперь, что нам делать с
foo.x
(доступ через переменную экземпляра)?
- Это может быть ошибка компиляции, как в С# или
- Это имеет значение. Поскольку
Foo.x
уже означает "простой доступ к x
", разумно, чтобы выражение foo.x
имело другое значение; то есть каждая часть выражения действительна и имеет доступ к x
.
Пусть надежда знает кто-то знающий настоящую причину. :-)