Понимание блока «наконец» - PullRequest
5 голосов
/ 12 июля 2010

Я написал семь тестовых случаев для понимания поведения блока 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 ()) не не работает?

Ответы [ 7 ]

11 голосов
/ 12 июля 2010

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

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

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

Так что попробуйте, если вы сделали:

finally {
            builder = null;
            return builder;
        }

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

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

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

2 голосов
/ 12 июля 2010

Блок 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 голосов
/ 12 июля 2010

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

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

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

try {...} 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!");
    }
}

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

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;
}
0 голосов
/ 12 июля 2010

Рассмотрим, что на самом деле делает компилятор для оператора 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 довольно явно сбивает с толку, поэтому, если у вас есть какой-либо выбор в этом вопросе, вам будет лучше переписать все так, чтобы все ваши операторы return выходили за пределы любой попытки-конечные блоки.

0 голосов
/ 12 июля 2010

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

0 голосов
/ 12 июля 2010

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

0 голосов
/ 12 июля 2010

StringBuilder не может быть нулевым, ожидается строковое значение.

Пустые аргументы обычно плохи при работе со строками. количество ++ не объявлено ??? builder.append ("") вы добавляете строку - хорошо. кол = кол ++;

...