Почему компилятор Java разрешает доступ к статической переменной через нулевой объект?

Я указывал некоторые трюки и наткнулся на это. В следующем коде:

public class TestClass1 {

    static int a = 10;

    public static void main(String ar[]){
        TestClass1 t1 = null ;
        System.out.println(t1.a); // At this line
    }
}
Объект

t1 null. Почему этот код не бросает NullPointerException?

Я знаю, что это не правильный способ доступа к переменным static, но вопрос о NullPointerException.

Ответ 1

Чтобы добавить дополнительную информацию к текущим ответам, если вы разобьете свой файл класса, используя:

javap -c TestClass1

Вы получите:

Compiled from "TestClass1.java"
public class TestClass1 extends java.lang.Object{
static int a;

public TestClass1();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   aconst_null
   1:   astore_1
   2:   getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   5:   aload_1
   6:   pop
   7:   getstatic   #3; //Field a:I
   10:  invokevirtual   #4; //Method java/io/PrintStream.println:(I)V
   13:  return

static {};
  Code:
   0:   bipush  10
   2:   putstatic   #3; //Field a:I
   5:   return
}

Здесь вы можете видеть, что доступ к статическому полю выполняется в строке 7 инструкцией getstatc. Когда доступ к статическому полю осуществляется через код, соответствующая команда getstatic будет сгенерирована в программном файле .class.

Инструкции

*static имеют особенность в том, что они не требуют ссылки на экземпляр объекта, который должен быть в стеке до их вызова (например, invokevirtual, который требует объектного ref в стеке), они разрешают поле/метод, используя только индекс в пуле постоянной времени выполнения, который позже будет использоваться для определения местоположения ссылки на поле.

Это техническая причина для предупреждения "Статическое поле должно быть доступным статическим образом", которое некоторые IDE будут бросать вам, когда вы пишете t1.a, потому что экземпляр объекта не нужен для разрешения статического поля.

Ответ 2

Нет необходимости в экземпляре при вызове статического члена или метода.

Так как статические члены принадлежат классу, а не экземпляру.

Нулевая ссылка может использоваться для доступа к переменной класса (статической), не вызывая исключения.

http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#d5e19846

Если вы видите пример (см. полный пример в спецификации)

 public static void main(String[] args) {
        System.out.println(favorite().mountain); //favorite() returns null
    }

Даже если результат функции favorite() равен null, исключение NullPointerException не выбрасывается. То, что печатается "Маунт", демонстрирует, что выражение Primary действительно полностью оценивается во время выполнения, несмотря на то, что для определения того, какое поле для доступа используется только его тип, а не его значение (потому что поле гора статическая).

Ответ 3

В чем причина NullPointerException в коде вроде этого:

  TestClass t = null;
  t.SomeMethod();

Если SomeMethod является методом экземпляра, он сделает что-то со стандартным эта ссылка:

  public void SomeMethod() {
    // Here we'll have a NullPointerException (since "this" is null)
    this.SomeField = ... // <- We usually omit "this" in such a code
  }

Так как это null, у нас будет исключение NullPointerException. Если метод, поле и т.д. является статическим, это гарантировало отсутствие этой ссылки, поэтому будет no NullPointerException

  public static void SomeStaticMethod() {
    // You can't put "this.SomeField = ..." here, because the method is static
    // Ans since you can't address "this", there no reason for NullPointerException
    ...
  }

  ...

  TestClass t = null; 
  // Equal to "TestClass.SomeStaticMethod();"
  t.SomeStaticMethod(); // <- "this" is null, but it not addressed

Ответ 4

Статические члены разделяются всеми экземплярами класса и не перераспределяются отдельно для каждого экземпляра. Они загружаются моментами загрузчика класса, с которыми встречается класс в первый раз. Связанный вопрос SO

Пожалуйста, имейте в виду, что эти статические члены не относятся к определенному экземпляру класса. Подробнее здесь. Я включил небольшой фрагмент кода для справки:

@NotThreadSafe
public class Test {

  // For all practical purpuose the following block will be only executed once at class load time.
  static {
   System.out.println("Loaded by the classloader : " + Test.class.getClassLoader());
  }

  // Keeps track of created instances.
  static int instanceCount;


  // A simple constructor that increments instance count.
  public Test(){
   Test.instanceCount++;
   System.out.println("instance number : " + instanceCount);
  }

  public static void main(String[] args) {
   System.out.println("Instaintiating objects");
   new Test(); new Test();
  }

  // Where would you expect this line to get printed? 
  // i.e last statement on the console or somewhere in the middle :)
  static {
    System.out.println("It should be printed at the end or would it?");
   }
 }

Ответ 5

t1.a эквивалентен TestClass1.a в этом случае, так как a является статическим членом (не членом экземпляра). Компилятор просматривает объявление t1, чтобы узнать, какой тип он есть, а затем просто рассматривает его так, как если бы вы использовали имя типа. Значение t1 никогда не проверяется. (Но если это вызов метода, например method(args).a, я думаю, что метод будет вызван. Но возвращаемое значение будет выбрано и никогда не будет проверено.) (Edit: Я проверил, что вызывается method(args) но исключение не возникает, если результат функции равен нулю.)

Ответ 6

Так как a - статический компилятор, он преобразует его в TestClass1.a. Для нестатических переменных он бы выбрал NullPointerException.

Ответ 7

Доступ к любому статическому члену можно получить с помощью имени класса Directly как TestClass1.a для него не требуется экземпляр

  System.out.println(TestClass1 .a);

Выход: 10