finally 블록에서 반환 된 변수를 변경해도 반환 값이 변경되지 않는 이유는 무엇입니까?


146

아래와 같이 간단한 Java 클래스가 있습니다.

public class Test {

    private String s;

    public String foo() {
        try {
            s = "dev";
            return s;
        } 
        finally {
            s = "override variable s";
            System.out.println("Entry in finally Block");  
        }
    }

    public static void main(String[] xyz) {
        Test obj = new Test();
        System.out.println(obj.foo());
    }
}

이 코드의 출력은 다음과 같습니다.

Entry in finally Block
dev  

블록 s에서 재정의되지 않고 finally인쇄 출력을 제어 하는 이유는 무엇 입니까?


11
당신이 나와 함께 마지막으로, 블록 반복에 return 문을 넣어해야 finally 블록은 항상 실행
후안 안토니오 고메즈 Moriano

1
try 블록에서 그것은 s를 반환
Devendra

6
진술의 순서가 중요합니다. s값을 변경하기 전에 돌아 옵니다.
피터 로리

6
그러나 C #과 달리 블록 에서 새 값을 반환 수 있습니다finally (할 수 없음)
Alvin Wong

답변:


167

try의 실행으로 완료 블록 return문과 값 s상기 시점에서 return문이 실행은 메소드에 의해 리턴 된 값이다. 이 finally절이 나중에 s(return 명령문이 완료된 은 그 시점에서 리턴 값을 변경하지 않습니다.

위의 내용 은 참조 하는 객체가 아니라 블록 s자체 의 값 변경을 처리합니다 . 경우 변경 가능한 객체에 대한 참조가 있었다 (이 아니다)하고 콘텐츠 오브젝트가 변경되었다 블록 다음 그 변경은 리턴 값을 보일 것이다.finallyssStringfinally

이 모든 작동 방식에 대한 자세한 규칙 은 Java 언어 사양의 14.20.2 섹션에 나와 있습니다. return명령문의 실행은 try블록 의 갑작스러운 종료로 계산됩니다 ( " 다른 이유로 인해 try 블록의 실행이 갑자기 완료되면 R .... "이 적용되는 섹션 ). 명령문이 갑작스러운 블록 종료 인 이유 는 JLS 14.17 섹션을 참조하십시오 return.

더 자세하게 말하면, 진술로 인해 문장 의 try블록과 finally블록이 try-finally갑자기 종료되면 return§14.20.2의 다음 규칙이 적용됩니다.

try다른 이유로 R [예외 발생] 외에 블록의 실행이 갑자기 완료되면finally 블록이 실행 된 다음 선택 사항이 있습니다.

  • 상기 중간 finally블록이 정상적으로 완료된 후,try 이유 R.위한 문이 완료 갑자기
  • 상기 중간 finally블록이 이유에 대해 S 급격 완료 후 try갑자기 이유 S에 대한 문이 완료 (이성 R은 폐기된다).

결과적으로 블록 의 return명령문 finally이 전체 try-finally명령문 의 리턴 값을 결정 하고 블록의 리턴 된 값 try이 삭제됩니다. 비슷한 일이 발생 try-catch-finally경우 문 try블록, 그것이 의해 잡힌 예외가 발생 catch블록을, 그리고 두 catch블록과 finally블록이 return문을.


finally 블록에 값을 추가하는 것보다 String 대신 StringBuilder 클래스를 사용하면 반환 값이 변경됩니다.
Devendra

6
@dev-나는 내 대답의 두 번째 단락에서 그것에 대해 토론합니다. 설명하는 상황에서 finally블록은 반환되는 객체 ( StringBuilder)를 변경하지 않지만 객체 의 내부를 변경할 수 있습니다. finally방법 전에 블록이 실행합니다 (비록 실제로 반환 return호출 코드가 반환 값을보기 전에 이러한 변경 사항이 발생할 문이 완료), 그래서.
테드 홉

List를 사용하면 StringBuilder와 같은 동작이 발생합니다.
Yogesh Prajapati

1
@yogeshprajapati-p. 같은 어떤 가변 반환 값 사실이다 ( StringBuilder, List, Set, 광고 nauseum) : 당신이에서 내용을 변경하는 경우 finally블록, 다음 해당 변경 사항은 전화 코드 방법 마침내 출구에서 볼 수 있습니다.
Ted Hopp

이것은 무슨 일 이 일어나는지, 왜 그런지 말하지 않습니다 (JLS에서 그것이 다루어 진 곳을 지적하면 특히 유용 할 것입니다).
TJ Crowder

65

마지막으로 호출하기 전에 반환 값이 스택에 배치되기 때문입니다.


3
사실이지만 반환 된 문자열이 변경되지 않은 이유에 대해서는 OP의 질문을 다루지 않습니다. 그것은 문자열 불변성 및 참조 대 객체를 스택에서 반환 값을 푸시하는 것 이상과 관련이 있습니다.
templatetypedef

두 문제는 모두 관련이 있습니다.
Tordek

2
@templatetypedef 문자열이 변경 가능하더라도을 사용 =하면 변경 되지 않습니다.
Owen

1
@Saul-Owen의 요점 (올바른)은 불변성과 OP의 finally블록이 반환 값에 영향을 미치지 않는 이유와 관련이 없다는 것입니다 . 나는 templatetypedef가 얻었을지도 모른다고 생각합니다 (이것은 명확하지 않지만) 반환 된 값은 불변의 객체에 대한 참조이기 때문에 finally블록 에서 코드를 변경하더라도 (다른 return명령문을 사용하는 것 외에 ) 메소드에서 리턴 된 값.
Ted Hopp

1
@templatetypedef 아닙니다. 문자열 불변 성과는 아무런 관련이 없습니다. 다른 유형에서도 마찬가지입니다. finally 블록에 들어가기 전에 return-expression을 평가하는 것과 관련이 있습니다. 즉, '스택에서 반환 값을 푸시'합니다.
Lorne의 후작

32

바이트 코드를 살펴보면 JDK가 크게 최적화되었으며 foo () 메서드가 다음과 같이 나타납니다.

String tmp = null;
try {
    s = "dev"
    tmp = s;
    s = "override variable s";
    return tmp;
} catch (RuntimeException e){
    s = "override variable s";
    throw e;
}

그리고 바이트 코드 :

0:  ldc #7;         //loading String "dev"
2:  putstatic   #8; //storing it to a static variable
5:  getstatic   #8; //loading "dev" from a static variable
8:  astore_0        //storing "dev" to a temp variable
9:  ldc #9;         //loading String "override variable s"
11: putstatic   #8; //setting a static variable
14: aload_0         //loading a temp avariable
15: areturn         //returning it
16: astore_1
17: ldc #9;         //loading String "override variable s"
19: putstatic   #8; //setting a static variable
22: aload_1
23: athrow

Java는 리턴하기 전에 "dev"문자열이 변경되지 않도록 보존했습니다. 실제로 여기에는 마침내 막히지 않습니다.


이것은 최적화가 아닙니다. 이것은 단순히 필요한 의미론의 구현입니다.
Lorne의 후작

22

여기에 주목할만한 두 가지가 있습니다.

  • 문자열은 변경할 수 없습니다. s를 "override variable s"로 설정하면 s가 s 객체의 고유 char 버퍼를 "override variable s"로 변경하지 않고 인라인 된 String을 참조하도록 설정합니다.
  • 호출 코드로 돌아 가기 위해 스택에 s에 대한 참조를 넣습니다. 그런 다음 (finally 블록이 실행될 때) 참조를 변경하면 스택에 이미있는 반환 값에 대해 아무 것도 수행하지 않아야합니다.

1
그래서 찌르는 것보다 stringbuffer를 가져 가면 무시됩니다.
Devendra

10
@dev- 절 에서 버퍼 의 내용 을 변경 finally하려면 호출 코드에서 볼 수 있습니다. 그러나에 새로운 문자열 버퍼를 할당 s하면 동작은 지금과 동일합니다.
테드 홉

마지막으로 블록 버퍼 변경에 문자열 버퍼를 추가하면 출력에 반영됩니다.
Devendra

@ 0xCAFEBABE 당신은 또한 매우 좋은 답변과 개념을 제공합니다, 나는 당신에게 전적으로 감사합니다.
Devendra

13

테드의 요점을 증명하기 위해 코드를 약간 변경합니다.

보시다시피 출력 s은 실제로 변경되었지만 반환 후에는 변경됩니다.

public class Test {

public String s;

public String foo() {

    try {
        s = "dev";
        return s;
    } finally {
        s = "override variable s";
        System.out.println("Entry in finally Block");

    }
}

public static void main(String[] xyz) {
    Test obj = new Test();
    System.out.println(obj.foo());
    System.out.println(obj.s);
}
}

산출:

Entry in finally Block 
dev 
override variable s

재정의 된 문자열이 반환되지 않는 이유는 무엇입니까?
Devendra

2
Ted와 Tordek은 이미 "마지막으로 실행되기 전에 반환 값을 스택에 넣습니다"라고 말했듯이
Frank

1
이것은 좋은 추가 정보이지만, 그 자체로 질문에 대답하지 않기 때문에 그것을 피하는 것을 꺼려합니다.
Joachim Sauer

5

기술적으로 말하면, returntry 블록 의 in은 finally블록이 정의 된 경우 무시되지 않으며 , 마지막으로 블록에 포함 된 경우에만 무시됩니다 return.

의심스러운 디자인 결정은 아마도 회고에서 실수 일 것입니다. 여러면에서이 동작은 무엇을 finally의미 하는지에 대한 구어체 이해와 정확히 일치 try합니다. "블록 에서 사전에 어떤 일이 발생하더라도 항상이 코드를 실행하십시오." 따라서 finally블록 에서 true를 반환 하면 전체 효과는 항상return s '아니요' .

일반적으로 이는 좋은 관용구가 finally아니며 자원을 정리 / 닫기 위해 블록을 자유롭게 사용해야 하지만 값을 반환하지 않는 경우는 거의 없습니다.


왜 모호한가? 다른 모든 상황에서 평가 순서와 일치합니다.
Lorne의 후작

0

이것을 시도하십시오 : s의 대체 값을 인쇄하려는 경우.

finally {
    s = "override variable s";    
    System.out.println("Entry in finally Block");
    return s;
}

1
경고 .. finally 블록이 정상적으로 완료되지 않음
Devendra
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.