Понимание 'наконец' блок

Я записал семь тестовых сценариев для понимания поведения finally блок. Какова логика позади как finally работы?

package core;

public class Test {
    public static void main(String[] args) {
        new Test().testFinally();
    }

    public void testFinally() {
        System.out.println("One = " + tryOne());
        System.out.println("Two = " + tryTwo());
        System.out.println("Three = " + tryThree());
        System.out.println("Four = " + tryFour());
        System.out.println("Five = " + tryFive());
        System.out.println("Six = " + trySix());
        System.out.println("Seven = " + trySeven());
    }

    protected StringBuilder tryOne() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder = null;
        }
    }

    protected String tryTwo() {
        String builder = "Cool";
        try {
            return builder += "Return";
        }
        finally {
            builder = null;
        }
    }

    protected int tryThree() {
        int builder = 99;
        try {
            return builder += 1;
        }
        finally {
            builder = 0;
        }
    }

    protected StringBuilder tryFour() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder.append("+1");
        }
    }

    protected int tryFive() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count++;
        }
        return count;
    }

    protected int trySix() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count = 1;
        }
        return count;
    }

    protected int trySeven() {
        int count = 0;
        try {
            count = 99;
            return count;
        }
        finally {
            count++;
        }
    }
}

Почему builder = null не работает?

Почему делает builder.append("+1") работа, тогда как count++trySeven ()), не работает?

5
задан Peter Mortensen 21 April 2013 в 20:30
поделиться

6 ответов

После того, как вы выполните возврат, единственный способ переопределить это - выполнить другой возврат (как обсуждалось в Возврат из блока finally в Java , это почти всегда плохая идея) или иным образом завершить круто. Ваши тесты никогда не возвращаются из файла finally.

JLS § 14.1 определяет внезапное завершение. Один из видов внезапного завершения - возврат. Блоки try в 1,2,3,4 и 7 внезапно завершаются из-за возвратов. Как объяснено в § 14.20.2 , если блок try завершается внезапно по причине R помимо выброса, немедленно выполняется блок finally.

Если блок finally завершается нормально (что, среди прочего, подразумевает отсутствие возврата), «оператор try завершается внезапно по причине R.». Другими словами, возврат, инициированный попыткой, остается неизменным; это относится ко всем вашим тестам. Если вы вернетесь из finally, «оператор try завершится внезапно по причине S (а причина R отбрасывается)». (S здесь - новый преобладающий возврат).

Итак, в tryOne, если вы это сделали:

finally {
            builder = null;
            return builder;
        }

этот новый возврат S переопределит исходный возврат R.

Для builder.append ("+ 1") в tryFour , имейте в виду, что StringBuilder является изменяемым, поэтому вы по-прежнему возвращаете ссылку на тот же объект, который указан в попытке. Вы просто делаете мутацию в последнюю минуту.

tryFive и trySix просты. Так как в try нет возврата, они оба завершаются нормально и выполняются так же, как если бы try-finally не было.

10
ответ дан 18 December 2019 в 13:10
поделиться

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

public void deleteRows(Connection conn) throws SQLException {
    Statement statement = conn.createStatement();
    try {
        statement.execute("DELETE * FROM foo");
    } finally {
        statement.close();
    }
}

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

try { ... } finally { ... Блоки finally { ... } предназначены для обеспечения того, что что-то всегда будет выполняться при завершении метода. Они наиболее полезны для случаев исключений. Если вы обнаружили, что делаете что-то подобное:

public String thisShouldBeRefactored(List<String> foo) {
    try { 
        if(foo == null) {
            return null;
        } else if(foo.length == 1) {
            return foo.get(0);
        } else {
            return foo.get(1);
        }
    } finally {
        System.out.println("Exiting function!");
    }
}

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

public String thisShouldBeRefactored(List<String> foo) {
    final String result;

    if(foo == null) {
        result = null;
    } else if(foo.length == 1) {
        result = foo.get(0);
    } else {
        result = foo.get(1);
    }

    System.out.println("Exiting function!");

    return result;
}
2
ответ дан 18 December 2019 в 13:10
поделиться

Блок finally выполняется, когда вы покидаете блок try. Оператор "return" выполняет две функции: во-первых, устанавливает возвращаемое значение функции, а во-вторых, выходит из функции. Обычно это выглядело бы как атомарная операция, но внутри блока try это вызовет выполнение блока finally после установки возвращаемого значения и до выхода из функции.

Возврат выполнения:

  1. Назначить возвращаемое значение
  2. выполнить окончательные блоки
  3. функция выхода

Пример первый (примитивный):

int count = 1;//Assign local primitive count to 1
try{
  return count; //Assign primitive return value to count (1)
}finally{
  count++ //Updates count but not return value
}

Пример второй (ссылка):

StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
try{
    return sb;//return a reference to StringBuilder
}finally{
    sb.append("hello");//modifies the returned StringBuilder
}

Пример третий (ссылка):

   StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
   try{
      return sb;//return a reference to StringBuilder
   }finally{
      sb = null;//Update local reference sb not return value
   }

Пример четвертый (возврат):

   int count = 1;   //assign count
   try{
      return count; //return current value of count (1)
   }finally{
      count++;      //update count to two but not return value
      return count; //return current value of count (2) 
                    //replaces old return value and exits the finally block
   }
2
ответ дан 18 December 2019 в 13:10
поделиться

Подумайте, что компилятор фактически делает для оператора return, например, в tryOne (): он копирует ссылку на builder обратно в среду вызывающей функции. После этого, но до того, как управление вернется к вызывающей функции, выполняется блок finally. Итак, на практике у вас есть что-то более похожее на это:

protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    try {
        builder.append("Cool");
        builder.append("Return");
        StringBuilder temp = builder;
        return temp;
    } finally {
        builder = null;
    }
}

Или, с точки зрения порядка, в котором фактически выполняются операторы (конечно, без учета возможных исключений), это выглядит примерно так:

protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    builder.append("Cool");
    builder.append("Return");
    StringBuilder temp = builder;
    builder = null;
    return temp;
}

Итак, установка builder = null работает, но ничего полезного не делает. Однако выполнение builder.append ("something") будет иметь видимый эффект , поскольку и temp, и builder относятся к одному и тому же (изменяемому) объекту.

Аналогично, то, что на самом деле происходит в trySeven (), выглядит примерно так:

protected int trySeven() {
    int count = 0;
    count = 99;
    int temp = count;
    count++;
    return temp;
}

В этом случае, поскольку мы имеем дело с int, копии независимы, поэтому увеличение одного не влияет на другое.

Тем не менее, факт остается фактом: размещение операторов return в блоке try-finally явно сбивает с толку, поэтому, если у вас есть какой-либо выбор в этом вопросе, вам лучше переписать все так, чтобы все ваши операторы возврата находятся за пределами блоков try-finally.

0
ответ дан 18 December 2019 в 13:10
поделиться

Почему builder = null не работает?
Потому что вы устанавливаете для локальной ссылки значение null, которое не изменит содержимое памяти. Итак, он работает, если вы попытаетесь получить доступ к построителю после блока finally, вы получите null.
Почему работает builder.append ("+ 1")?
Поскольку вы изменяете содержимое памяти, используя ссылку, это должно работать.
Почему count ++ не работает в testFive ()?
У меня он работает нормально. Как и ожидалось, он выводит 100.

0
ответ дан 18 December 2019 в 13:10
поделиться

builder = null и builder.append("+1") работают. Просто они не влияют на то, что вы возвращаете. Функция возвращает то, что имеет оператор return, независимо от того, что происходит потом.

Причина разницы в том, что builder передается по ссылке. builder=null изменяет локальную копию builder. builder.append("+1") влияет на копию, хранящуюся у родителя.

0
ответ дан 18 December 2019 в 13:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: