Ошибка недостижимого кода и предупреждение о мертвом коде в Java в Eclipse?

Кто-нибудь знает почему:

public void foo()
{
    System.out.println("Hello");
    return;
    System.out.println("World!");
}

Будет сообщаться как "недостижимая ошибка" в Eclipse, но

public void foo()
{
    System.out.println("Hello");
    if(true) return;
    System.out.println("World!");
}

Только вызывает предупреждение "Мертвый код"?

Единственное объяснение, которое я могу придумать, заключается в том, что компилятор Java помечает только первое, а дополнительный анализ в Eclipse вычисляет второе. Однако, если это так, почему компилятор Java не может выяснить этот случай во время компиляции?

Разве компилятор Java не выяснит во время компиляции, что if (true) не имеет никакого эффекта, таким образом, получая байт-код, который по существу идентичен? В какой момент применяется анализ достижимого кода?

Я думаю, что более общий подход к этому вопросу: "когда применяется анализ достижимого кода"? При преобразовании второго фрагмента кода Java в конечный байт-код я уверен, что в какой-то момент эквивалент времени выполнения "if(true)" будет удален, и представления двух программ станут идентичными. Разве Java-компилятор не применяет анализ достижимого кода снова?

8 ответов

Решение

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

Что касается того, почему Eclipse обнаруживает мертвый код, то это просто удобство интегрированного инструмента разработки со встроенным компилятором, который можно настраивать в большей степени, чем JDK, для обнаружения такого рода кода.

Обновление: JDK фактически устраняет мертвый код.

public class Test {
    public void foo() {
        System.out.println("foo");
        if(true)return;
        System.out.println("foo");
    }
    public void bar() {
        System.out.println("bar");
        if(false)return;
        System.out.println("bar");
    }
}

javap -c говорит:

Открытый класс Test расширяет java.lang.Object{
публичный тест ();
  Код:
   0:   aload_0
   1:   invokespecial   #1; // Метод java/lang/Object."":()V
   4: возврат

public void foo();
  Код:
   0: гестатик #2; // Поле java/lang/System.out:Ljava/io/PrintStream;
   3:   ldC#3; // Строка foo
   5:   invokevirtual   #4; // Метод java/io/PrintStream.println:(Ljava/lang/StrV
   8: возвращение

public void bar();
  Код:
   0: гестатик #2; // Поле java/lang/System.out:Ljava/io/PrintStream;
   3:   ldC#5; // Строка бар
   5: invokevirtual # 4; // Метод java/io/PrintStream.println:(Ljava/lang/String;)V
   8: Гетстатик № 2; // Поле java/lang/System.out:Ljava/io/PrintStream;
   11:  ldC#5; // Строка бар
   13:  invokevirtual   #4; // Метод java/io/PrintStream.println:(Ljava/lang/String;)V
   16: возвращение

}

Что касается того, почему он (Sun) не дает предупреждения об этом, я понятия не имею:) По крайней мере, компилятор JDK имеет встроенную функцию DCE (Dead Code Elmination).

Недоступный код - это ошибка согласно спецификации языка Java.

Цитировать из JLS:

Идея состоит в том, что должен существовать некоторый возможный путь выполнения от начала конструктора, метода, инициализатора экземпляра или статического инициализатора, который содержит инструкцию, до самой инструкции. Анализ учитывает структуру высказываний. За исключением специальной обработки while, do и операторов, выражение условия которых имеет постоянное значение true, значения выражений не учитываются в анализе потока.

Что это значит? if блок не учитывается, так как если вы идете по одному из путей if заявление, вы можете достичь окончательного заявления печати. Если вы изменили свой код на:

public void foo() {
    System.out.println("Hello");
    if (true)
        return;
    else
        return;
    System.out.println("World!");
}

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

То есть Java-совместимому компилятору не разрешено компилировать ваш первый фрагмент кода. Чтобы далее процитировать JLS:

Например, следующий оператор приводит к ошибке времени компиляции:

while (false) { x=3; }

потому что утверждение х = 3; не достижимо; но внешне похожий случай:

if (false) { x=3; }

не приводит к ошибке времени компиляции. Оптимизирующий компилятор может понять, что оператор x=3; никогда не будет выполнен и может пропустить код для этого оператора из сгенерированного файла класса, но оператор x=3; не считается "недоступным" в техническом смысле, указанном здесь.

Второе предупреждение, которое Eclipse дает о мертвом коде, - это предупреждение, генерируемое компилятором, которое, согласно JLS, не является "недоступным", но на практике это так. Это дополнительная проверка стиля линта, которую предоставляет Eclipse. Это совершенно необязательно, и с помощью конфигурации Eclipse может быть отключено или превращено в ошибку компилятора вместо предупреждения.

Этот второй блок представляет собой "кодовый запах", if (false) блоки обычно вставляются, чтобы отключить код для целей отладки, его оставление обычно является случайным, и, следовательно, предупреждение.

На самом деле, Eclipse проводит еще более сложные тесты, чтобы определить возможные значения оператора if, чтобы определить, можно ли выбрать оба пути. Например, Eclipse также будет жаловаться на мертвый код в следующем методе:

public void foo() {
    System.out.println("Hello");
    boolean bool = Random.nextBoolean();
    if (bool)
        return;
    if (bool || Random.nextBoolean())
      System.out.println("World!");
}

Он сгенерирует недоступный код для второго оператора if, поскольку это может привести к тому, что bool должен быть только false на данный момент в коде. В таком коротком фрагменте кода очевидно, что два оператора if проверяют одно и то же, однако, если в середине 10-15 строк кода, это может быть уже не так очевидно.

Итак, в общем, разница между ними: одна запрещена JLS, а другая нет, но обнаружена Eclipse как услуга для программиста.

Это делается для того, чтобы обеспечить своего рода условную компиляцию.
Это не ошибка с if, но компилятор отметит ошибку для while, do-while а также for,
Хорошо:

if (true) return;    // or false
System.out.println("doing something");

Это ошибки

while (true) {
}
System.out.println("unreachable");

while (false) {
    System.out.println("unreachable");
}

do {
} while (true);
System.out.println("unreachable");

for(;;) {
}
System.out.println("unreachable");

Это объясняется в конце JLS 14.21: недостижимые утверждения:

Обоснование этого отличающегося подхода состоит в том, чтобы позволить программистам определять "переменные флага", такие как:

 static final boolean DEBUG = false;

а затем написать код, такой как:

   if (DEBUG) { x=3; }

Идея состоит в том, что должна быть возможность изменить значение DEBUG с false на true или с true на false, а затем правильно скомпилировать код без каких-либо других изменений в тексте программы.

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

позволяющий if (true) return; хороший способ обойти ограничение JLS, если вы действительно хотите сделать это специально. Если JLS остановит это, это будет мешать. Кроме того, следует также прекратить:

 public static boolean DEBUG = true; //In some global class somewhere else


 ...

 if (DEBUG) return; //in a completely unrelated class.

 ...

Потому что константа DEBUG полностью встроена и функционально эквивалентна простому вводу true в условии if. С точки зрения JLS эти два случая очень похожи.

if (true) немного более тонкий, чем "недоступный"; потому что это жестко return всегда сделает следующий код недоступным, но при изменении условия в if может сделать следующее утверждение достижимым.

Наличие условного обозначения означает, что условие может измениться. Есть случаи, когда что-то более сложное, чем true находится в скобках, и для читателя не очевидно, что следующий код "мертвен", но компилятор замечает, поэтому он может предупредить вас об этом.

Eclipse упоминается здесь, и он делает вещи более сложными для пользователя; но на самом деле под Eclipse находится (очень сложный) Java-компилятор, который содержит множество переключателей для предупреждений и т. д., которые Eclipse может включать и выключать. Другими словами, вы не получаете достаточно различных предупреждений / ошибок от прямой javac компилировать, и у вас нет удобных средств для их включения или выключения. Но это та же самая сделка, только с большим количеством наворотов.

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

В вашем первом примере это ошибка, потому что код недопустим по определению java. Компилятор не позволяет создавать байт-код с недостижимыми операторами.

Если вы хотите игнорировать предупреждение "предупреждение о мертвом коде в Java под Eclipse", выполните в eclipse следующее:

  1. Нажмите Window-Preferences-Java-Compiler-Errors/Warnings
  2. Нажмите на "Потенциальные проблемы программирования"
  3. Выберите "Игнорировать" "Мертвый код, например, если (false)"
  4. Нажмите Применить
  5. Нажмите ОК

Сохраните и закройте свою IDE Eclipse Когда вы снова откроете Eclipse, эти конкретные предупреждения больше не должны быть перечислены.

* В этом примере решения я использую Eclipse IDE для разработчиков Java - Версия: Mars.2 Release (4.5.2)

Я сделал несколько попыток затмения и думаю, что есть три вида обработки мертвого кода JDK: 1) нет предупреждений, 2) предупреждений и 3) ошибок.

Для типичного кода условной компиляции "IF" JDK обнаружил это и не сообщил о нем как о мертвом коде. Для мертвого кода, вызванного постоянным логическим флагом, JDK обнаруживает это и сообщает об этом на уровне предупреждения. Для мертвого кода, вызванного потоком управления программы, JDK обнаружит его как ошибку.

Ниже моя попытка:

    public class Setting {
        public static final boolean FianlDebugFlag = false;
    }


    class B {
    .....

    // no warn, it is typical "IF" conditional compilataion code
    if(Setting.FianlDebugFlag) 
        System.out.println("am i dead?");   
    if(false) 
        System.out.println("am i dead?");   


    // warn, as the dead code is caused by a constant boolean flag
    if(ret!=null && Setting.FianlDebugFlag) 
        System.out.println("am i dead?");   

    if(Setting.FinalDebug)                  
        return null;                                                            
    System.out.println("am i dea?");        

    // error, as the dead code is due to the program's control flow
    return null;
    System.out.println("am i dead");        
    }
Другие вопросы по тегам