Как сказать Java, что переменная не может быть нулевой? - PullRequest
1 голос
/ 15 мая 2019

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

boolean[] stuffNThings;
int state=1;
for(String string:list){
   switch(state){
      case 1:
         if(/*condition*/){
            // foo
            break;
         }else{
            stuffNThings=new boolean[/*size*/];
            state=2;
         }
      // intentional fallthrough
      case 2:
         // bar
         stuffNThings[0]=true;
   }
}

Как вы, человек, видите, случай 2 случается только тогда, когда ранее было состояние 1, и он переключался в состояние 2 послеинициализация массива.Но Eclipse и компилятор Java этого не видят, потому что для них это выглядит довольно сложной логикой.Поэтому Eclipse жалуется:

Локальная переменная stuffNThings, возможно, не была инициализирована. "

И если я изменю" boolean[] stuffNThings; "на" boolean[] stuffNThings=null; ", этопереключается на это сообщение об ошибке:

Потенциальный доступ к нулевому указателю: переменная stuffNThings может быть нулевой в этом месте.

Я также не могу инициализировать его сверху,потому что размер массива определяется только после последнего цикла в состоянии 1.

Java считает, что массив может быть нулевым, но я знаю, что это невозможно. Есть ли способ сообщить Java об этом? Или я определенно вынужден поставить бесполезную проверку null вокруг этого? Добавление, которое усложняет понимание кода, поскольку похоже, что может быть случай, когда значение на самом деле не будет установлено в true.

Ответы [ 4 ]

5 голосов
/ 15 мая 2019

Java считает, что массив может быть нулевым, но я знаю, что это невозможно.

Строго говоря, Java считает, что переменная может быть неинициализирована .Если это не определенно инициализировано, значение не должно быть наблюдаемым .

(Независимо от того, инициализирована ли переменная без вывода сообщений null или оставлена ​​в неопределенном состоянии , это деталь реализации. Дело в том, что язык говорит, что вы не должны видетьзначение.)

Но в любом случае, решение состоит в том, чтобы инициализировать его как null.Это избыточно, но нет никакого способа сказать Java «просто доверься мне, оно будет инициализировано».


В вариантах, в которых вы получаете сообщения «Доступ к потенциальному нулевому указателю»:

  1. Это предупреждение, а не ошибка.
  2. Вы можете игнорировать или подавлять предупреждение.(Если ваш анализ правильности неверен, в результате вы можете получить NPE. Но это ваш выбор.)
  3. Вы можете отключить некоторые или все предупреждения с помощью переключателей компилятора.
  4. Выможет подавить определенное предупреждение с аннотацией @SuppressWarnings:

    • Для Eclipse используйте @SuppressWarnings("null").
    • Для Android используйте @SuppressWarnings("ConstantConditions").

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

  5. Возможно, вы сможете реструктурировать код.

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

В любом случае, вы можете легко избежать необходимости выполнять сквозную настройку путем реструктуризации кода.Скопируйте код в корпусе case 2: в конец корпуса case 1:.Исправлена.Двигайтесь дальше.


Обратите внимание, что ошибка «возможно, не инициализирована» не является глупостью компилятора Java.Есть целая глава JLS о правилах для определенного назначения и так далее.Компилятору Java не разрешено быть умным, потому что это будет означать, что один и тот же код Java будет легальным или недопустимым, в зависимости от реализации компилятора.Это было бы плохо для переносимости кода.

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

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

0 голосов
/ 15 мая 2019

Более понятным было бы:

    boolean[] stuffNThings;
    boolean initialized = false;
    for (String string: list) {
        if (!initialized) {
            if (!/*condition*/) {
                stuffNThings = new boolean[/*size*/];
                initailized = true;
            }
        }
        if (initialized) {
            // bar
            stuffNThings[0] = true;
        }
    }

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

Это прощеанализ потока (по сравнению с переключателем с провалом).

Кроме того, вместо boolean[] можно также использовать BitSet (поскольку он не имеет фиксированного размера в виде массива).

BitSet stuffNThings = new BitSet(/*max size*/);
0 голосов
/ 15 мая 2019

После простой попытки выполнения кода независимо от жалоб Eclipse, я заметил, что он действительно работает без проблем. Поэтому, очевидно, это было просто предупреждение, установленное на уровне «ошибка», несмотря на то, что оно не критично.
Была кнопка «Настроить серьезность проблемы», поэтому я установил серьезность «Доступ к потенциальному нулевому указателю» на «Предупреждение» (и соответственно изменил некоторые другие уровни). Теперь Eclipse просто помечает это как предупреждение и выполняет код без жалоб.

0 голосов
/ 15 мая 2019

Отдельный отказ от ответа: когда код «настолько» сложен, что компилятор IDE / Java не «видит его», то это хороший признак того, что ваш код слишком сложен в любом случае.По крайней мере, для меня не было очевидно, что ты сказал.Мне приходилось многократно читать вверх и вниз, чтобы убедить себя в том, что приведенное в вопросе утверждение верно.

У вас есть переключатель if в for.Чистый код и «единственный уровень абстракции» скажут вам: не очень хорошая отправная точка.

Посмотрите на свой код.У вас есть замаскированный конечный автомат 1008 *.Спросите себя, стоит ли реорганизовать это в более широком масштабе, например, превратив его в явный какой-то конечный автомат.

Еще одна менее навязчивая идея: использовать список вместомассив.Затем вы можете просто создать пустой список и добавить к нему элементов по мере необходимости.

...