Подтвердить что ты не робот

Как работает Java System.exit() с блоками try/catch/finally?

Мне известны головные боли, связанные с возвратом в try/catch/finally blocks - случаи, когда return в конечном итоге всегда является возвратом метода, даже если возврат в блок try или catch должен быть выполнен.

Однако, что же касается System.exit()? Например, если у меня есть блок try:

try {
    //Code
    System.exit(0)
}
catch (Exception ex) {
    //Log the exception
}
finally {
    System.exit(1)
}

Если нет исключений, которые вызовет System.exit()? Если выход был оператором return, тогда строка System.exit(1) всегда вызывалась бы (?). Тем не менее, я не уверен, что выход ведет себя иначе, чем возврат.

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

4b9b3361

Ответ 1

Нет. System.exit(0) не возвращается, и блок finally не выполняется.

System.exit(int) может выбросить SecurityException. Если это произойдет, блок finally будет выполнен. И поскольку тот же самый главный вызов вызывает тот же метод из одной и той же базы кода, другой SecurityException, скорее всего, будет удален из второго вызова.


Вот пример второго случая:

import java.security.Permission;

public class Main
{

  public static void main(String... argv)
    throws Exception
  {
    System.setSecurityManager(new SecurityManager() {

      @Override
      public void checkPermission(Permission perm)
      {
        /* Allow everything else. */
      }

      @Override
      public void checkExit(int status)
      {
        /* Don't allow exit with any status code. */
        throw new SecurityException();
      }

    });
    System.err.println("I'm dying!");
    try {
      System.exit(0);
    } finally {
      System.err.println("I'm not dead yet!");
      System.exit(1);
    }
  }

}

Ответ 2

Простые тесты, включая catch, также показывают, что если system.exit(0) не генерирует исключение безопасности, это будет последний выполненный оператор (catch и finally вообще не выполняются).

Если system.exit(0) генерирует исключение безопасности, выполняются инструкции catch и finally. Если оба catch и finally содержат операторы system.exit(), выполняются только инструкции, предшествующие этим операторам system.exit().

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

Подробнее здесь (личный блог).

Ответ 3

окончательный блок будет выполнен независимо от того, что.... даже если try block выбрасывает любой throwable (исключение или ошибку).....

только случай, когда блок finally не выполняется... это когда мы вызываем метод System.exit().

try{
    System.out.println("I am in try block");
    System.exit(1);
} catch(Exception ex){
    ex.printStackTrace();
} finally {
    System.out.println("I am in finally block!!!");
}

Он не выполнит окончательный блок. Программа будет прекращена после оператора System.exit().

Ответ 4

В других ответах рассказывается, как блоки catch и finally не запускаются, если System.exit выходит из JVM, не бросая SecurityException, но они не показывают, что происходит в "try-with- ресурсов" блокировать ресурсы: закрыты ли они?

В соответствии с JLS, раздел 14.20.3.2:

Эффект перевода заключается в том, чтобы указать спецификацию ресурса "внутри" инструкции try. Это позволяет заключить предложение catch расширенного оператора try-with-resources для исключения исключения из-за автоматической инициализации или закрытия любого ресурса.

Кроме того, все ресурсы будут закрыты (или будут закрыты) к моменту завершения блока finally в соответствии с намерением ключевого слова finally.

То есть, ресурсы будут close d до запуска блока catch или finally. Что делать, если они close d каким-то образом, даже если catch и finally не запускаются?

Здесь приведен код, демонстрирующий, что ресурсы в операторе "try-with-resources" также не закрыты.

Я использую простой подкласс BufferedReader, который печатает выражение перед вызовом super.close.

class TestBufferedReader extends BufferedReader {
    public TestBufferedReader(Reader r) {
        super(r);
    }

    @Override
    public void close() throws IOException {
        System.out.println("close!");
        super.close();
    }
}

Затем я установил тестовый пример вызова System.exit в инструкции try-with-resources.

public static void main(String[] args)
{
    try (BufferedReader reader = new TestBufferedReader(new InputStreamReader(System.in)))
    {
        System.out.println("In try");
        System.exit(0);
    }
    catch (Exception e)
    {
        System.out.println("Exception of type " + e.getClass().getName() + " caught: " + e.getMessage());
    }
    finally
    {
        System.out.println("finally!");
    }
}

Вывод:

В попытке

Следовательно, не только блоки catch и finally не запускаются, инструкция "try-with-resources" не получит возможность close его ресурсов, если System.exit будет успешным.

Ответ 5

  • В приведенном ниже примере, если System.exit(0) находится перед строкой исключения, программа будет завершена нормально, поэтому FINALLY не будет выполняться.

  • Если System.exix(0) - последняя строка блока try, здесь мы имеем 2 сценария

    • когда присутствует исключение, тогда выполняется окончательный блок
    • когда исключение отсутствует, тогда, наконец, блок не выполняется

.

package com.exception;

public class UserDefind extends Exception {
private static int accno[] = {1001,1002,1003,1004,1005};

private static String name[] = {"raju","ramu","gopi","baby","bunny"};

private static double bal[] = {9000.00,5675.27,3000.00,1999.00,1600.00};
UserDefind(){}

UserDefind(String str){
    super(str);
}


public static void main(String[] args) {
    try {
        //System.exit(0); -------------LINE 1---------------------------------
        System.out.println("accno"+"\t"+"name"+"\t"+"balance");

        for (int i = 0; i < 5; i++) {
            System.out.println(accno[i]+"\t"+name[i]+"\t"+bal[i]);
            //rise exception if balance < 2000
            if (bal[i] < 200) {
                UserDefind ue = new UserDefind("Balance amount Less");
                throw ue;
            }//end if
        }//end for
        //System.exit(0);-------------LINE 2---------------------------------

    }//end try
    catch (UserDefind ue)
    {
        System.out.println(ue);
    }
    finally{
        System.out.println("Finnaly");
        System.out.println("Finnaly");
        System.out.println("Finnaly");
    }
}//end of main

}//end of class

Ответ 6

Если вы считаете это поведение проблематичным, и вам нужен тонкий контроль над вашими вызовами System.exit, то единственное, что вы можете сделать, это обернуть функциональность System.exit в вашей собственной логике. Если мы это сделаем, мы можем получить, наконец, выполненные блоки и получить ресурсы в рамках нашего потока.

То, что я рассматриваю, - это обернуть вызов и функции System.exit в моем собственном статическом методе. В моей реализации exit я бы выбрал пользовательский подкласс Throwable или Error и реализовать обработчик исключений Uncaught с Thread.setDefaultUncaughtExceptionHandler для обработки этого исключения. Таким образом, мой код становится:

 
//in initialization logic:
Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> {
  if(exception instanceof SystemExitEvent){
    System.exit(((SystemExitEvent)exception).exitCode);
  }
})

// in "main flow" or "close button" or whatever
public void mainFlow(){
  try {
    businessLogic();
    Utilities.exit(0);
  }
  finally {
    cleanUpFileSystemOrDatabaseConnectionOrWhatever();  
  }
}

//...
class Utilities {

  // I'm not a fan of documentaiton, 
  // but this method could use it.
  public void exit(int exitCode){
    throw new SystemExitEvent(exitCode);
  }
}

class SystemExitEvent extends Throwable { 
  private final int exitCode;

  public SystemExitEvent(int exitCode){
    super("system is shutting down")
    this.exitCode = exitCode;
  }
} 

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

//kotlin, a really nice language particularly for testing on the JVM!

@Test fun `when calling business logic should business the business`(){
  //setup
  val underTest = makeComponentUnderTest(configureToReturnExitCode = 42);

  //act
  val thrown: SystemExitEvent = try {
    underTest.mainFlow();
    fail("System Exit event not thrown!")
  }
  catch(event: SystemExitEvent){
    event;
  }

  //assert
  assertThat(thrown.exitCode).isEqualTo(42)

Основным недостатком этой стратегии является то, что это способ получить функциональность из потока исключений, что часто имеет непреднамеренные последствия. Наиболее очевидным в этом случае является то, что везде, где вы написали try { ... } catch(Throwable ex){ /*doesnt rethrow*/ }, нужно будет обновить. В случае библиотек, которые имеют контексты пользовательского исполнения, их необходимо будет доработать, чтобы также понять это исключение.

В целом, это кажется хорошей стратегией для меня. Кто-нибудь еще так думает?