Для элемента массива, на который ссылается неназначенный элемент, содержащий объект, не возникает исключений

#java #arrays #dynamic-arrays

Вопрос:

MRP

 class B {
    static int v;
    
    public B(int i) {
        System.out.format("Constructor called with value %dn", i);
        v=i;
    }
}

public class A {
    static B[] c;
    
    A(){
        c=new B[5];
        
        c[1]=new B(1);
    
        for (int i=0; i<3; i  ) {
            System.out.format("c[%d] is %dn", i, c[i].v);
        }
        
        c[2]=new B(2);
        
        for (int i=0; i<3; i  ) {
            System.out.format("c[%d] is %dn", i, c[i].v);
        }
    }
    
    public static void main(String[] args) {
        new A();
    }
}

Output is:

Constructor called with value 1
c[0] is 1
c[1] is 1
c[2] is 1
Constructor called with value 2
c[0] is 2
c[1] is 2
c[2] is 2
 

Ожидалось бы, что исключение будет вызвано ссылкой на неназначенные элементы массива, например, c[0]. Значения массива также неправильно изменены предыдущим назначением. c[0] никогда не присваивается значение, но принимает значения 1 и 2 в выводе выше.

 
public class A {
    static String[] c;
    
    A(){
        c=new String[5];
        
        c[0]=new String("alpha");
    
        for (int i=0; i<3; i  ) {
            System.out.format("c[%d] is %sn", i, c[i]);
        }
        
        c[1]=new String("beta");
        
        for (int i=0; i<3; i  ) {
            System.out.format("c[%d] is %sn", i, c[i]);
        }
    }
    
    public static void main(String[] args) {
        new A();
    }
}


Output for the above is:

c[0] is alpha
c[1] is null
c[2] is null
c[0] is alpha
c[1] is beta
c[2] is null
 

Другое поведение наблюдается для строкового объекта в приведенном выше примере.

Ответ №1:

Таким образом, вопрос в том, почему это не c[i].v приводит к NullPointerException тому, когда c[i] это null происходит .

Давайте начнем с чего-нибудь немного более простого:

 B b = null;
System.out.println(b.v);
 

Это не будет бросать NullPointerException .

Почему?

Поскольку v поле B есть static , нам не нужно разыменовывать значение b , чтобы получить значение v . Значение v не связано с каким-либо конкретным экземпляром B .

Так, по сути, b.v и B.v есть эквиваленты.


В более общем случае рассмотрим, что <expr> это какое-то выражение, статическим типом которого является B . Затем:

   V v = <expr>.v
 

имеет тот же эффект, что и:

   B temp = <expr>;
  V v = B.v;
 

Другими словами, выражение вычисляется, и его значение отбрасывается. Затем берется значение статического поля. Но поскольку temp разыменование не разыменовано (потому что в этом нет необходимости), в случае, если выражение равно нулю, NPE не будет … как это происходит в вашем примере.


Разница в вашем примере со строкой заключается в том, что вы печатаете состояние String экземпляра, а не состояние static поля. И в конкатенации строк не возникает NPE, потому что оператор сопоставляет null to "null" , а не вызывает null.toString() .


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

Действительно, некоторые средства проверки стиля Java / статические анализаторы отметят это как плохой стиль или возможную ошибку.

Комментарии:

1. Хорошо, я все понял. Таким образом, тот факт, что статическое поле не связано с экземпляром, означает, что все экземпляры B, на которые ссылаются значения массива c[0], c[1] и c[2], разрешаются в статическое поле.

2. Это верно. (Это один из тех случаев, когда я думаю, что они немного неправильно поняли Java. IMO b.v должна быть ошибкой компиляции, если v static это поле, а b не класс, который объявляет v . Это позволило бы избежать проблем, подобных вашим …)