효과적으로 최종 대 최종-다른 동작


104

지금까지 나는 사실상 최종최종 이 어느 정도 동등하며 JLS가 실제 동작에서 동일하지 않으면 유사하게 취급 할 것이라고 생각했습니다. 그런 다음이 인위적인 시나리오를 발견했습니다.

final int a = 97;
System.out.println(true ? a : 'c'); // outputs a

// versus

int a = 97;
System.out.println(true ? a : 'c'); // outputs 97

분명히 JLS는 여기에서 둘 사이에 중요한 차이를 만들고 그 이유를 모르겠습니다.

나는 다른 스레드를 읽었다.

그러나 그들은 그러한 세부 사항을 다루지 않습니다. 결국, 더 넓은 수준에서 그것들은 거의 동등하게 보입니다. 그러나 더 깊이 파고 들면 분명히 다릅니다.

이 동작의 원인은 무엇입니까? 누구든지 이것을 설명하는 JLS 정의를 제공 할 수 있습니까?


편집 : 다른 관련 시나리오를 찾았습니다.

final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true

// versus

String a = "a";
System.out.println(a + "b" == "ab"); // outputs false

따라서 문자열 인턴도 여기에서 다르게 동작합니다 (실제 코드에서이 코드 조각을 사용하고 싶지 않고 다른 동작에 대해 궁금합니다).


2
매우 흥미로운 질문입니다! Java가 두 경우 모두 동일하게 작동 할 것으로 예상하지만 이제 깨달았습니다. 이 항상 행동이라면 나 자신을 요구하고 있거나 이전 버전과 다른 경우
리노

8
아래의 큰 대답의 마지막 견적 문구 @Lino 동일한 모든 방법 돌아 자바 6 : "피연산자 중 하나가 유형 인 경우 T 여기서 T는 이다 byte, short또는 char, 다른 피연산자의 상수 표현이다 입력 int값이 타입에서 표현할 수이고 T는 , 조건식의 다음 유형은 T . " --- 심지어 Berkeley에서 Java 1.0 문서를 찾았습니다. 같은 텍스트 . --- 예, 항상 그랬습니다.
Andreas

1
물건을 "찾는"방식은 흥미 롭습니다 : P 천만에요 :)
phant0m

답변:


66

우선, 우리는 지역 변수에 대해서만 이야기하고 있습니다. 사실상 최종 은 필드에 적용되지 않습니다. final필드에 대한 의미 체계 가 매우 다르고 많은 컴파일러 최적화 및 메모리 모델 약속이 적용 되기 때문에 이것은 중요 합니다. 최종 필드의 의미 체계에 대해서는 $ 17.5.1 을 참조하십시오 .

표면 수준 finaleffectively final지역 변수의 경우 실제로 동일합니다. 그러나 JLS는 이와 같은 특수한 상황에서 실제로 광범위한 효과를 갖는 둘을 명확하게 구분합니다.


전제

변수 에 대한 JLS§4.12.4 에서 final:

상수 변수 A는 final가변 기본 유형 또는 입력 문자열 로 초기화 상수 식 ( §15.29을 ). 변수가 상수 변수인지 여부는 클래스 초기화 ( §12.4.1 ), 이진 호환성 ( §13.1 ), 도달 가능성 ( §14.22 ) 및 명확한 할당 ( §16.1.1 ) 과 관련하여 영향미칠 수 있습니다 .

int은 원시적 이므로 변수 a는 이러한 상수 변수 입니다.

또한, 같은 장에서 effectively final:

final로 선언되지 않은 특정 변수는 대신 효과적으로 final로 간주됩니다. ...

이 말로하는 방식에서 그래서, 다른 예에서 분명하다 a되어 있지 그대로, 일정한 변수를 고려 최종하지 ,하지만 에만 효과적으로 마지막.


행동

이제 구별이 생겼으니 무슨 일이 일어나고 있고 왜 출력이 다른지 살펴 보겠습니다.

? :여기서 조건부 연산자를 사용하고 있으므로 정의를 확인해야합니다. 에서 JLS§15.25 :

두 번째 및 세 번째 피연산자 식에 따라 부울 조건식 , 숫자 조건식참조 조건식의 세 가지 종류의 조건식이 있습니다.

이 경우, 우리는 얘기 숫자 조건식 에서, JLS§15.25.2 :

숫자 조건식의 유형은 다음과 같이 결정됩니다.

그리고 그것은 두 사건이 다르게 분류되는 부분입니다.

효과적으로 최종

effectively final이 규칙과 일치하는 버전 :

그렇지 않으면 일반 숫자 승격 ( §5.6 )이 두 번째 및 세 번째 피연산자에 적용되고 조건식의 유형은 두 번째 및 세 번째 피연산자의 승격 된 유형입니다.

어떤 당신이 할 것인지와 같은 동작입니다 5 + 'd'즉, int + char에서 어떤 결과 int. JLS§5.6 참조

숫자 승격은 숫자 컨텍스트에있는 모든 표현식의 승격 유형을 결정합니다. 승격 된 유형은 각 표현식이 승격 된 유형으로 변환 될 수 있도록 선택되며 산술 연산의 경우 승격 된 유형의 값에 대해 연산이 정의됩니다. 숫자 컨텍스트에서 표현식의 순서는 숫자 승격에 중요하지 않습니다. 규칙은 다음과 같습니다.

[...]

다음으로 확장 기본 변환 ( §5.1.2 ) 및 축소 기본 변환 ( §5.1.3 )이 다음 규칙에 따라 일부 표현식에 적용됩니다.

숫자 선택 컨텍스트에서 다음 규칙이 적용됩니다.

어떤 식 형인 경우 int이고 일정하지 식 ( §15.29 ) 다음 추진 유형은 int한 유형이 아닌 다른 표현 int거칩니다 프리미티브 확대 변환 에이 int.

따라서 모든 것이 이미 있는 int그대로 승진됩니다 . 그것은의 출력을 설명합니다 .aint97

결정적인

final변수가 있는 버전 은 다음 규칙과 일치합니다.

피연산자 중 하나가 is , 또는 또는 인 유형 T이고 다른 피연산자가 값 을 유형 으로 표현할 수있는 유형 의 상수 식 ( §15.29 ) 인 경우 조건식의 유형은입니다 .TbyteshortcharintTT

최종 변수 a는 유형 int이고 상수 표현식입니다 (이니까 final). 으로 표현할 수 char있으므로 결과는 유형 char입니다. 이것으로 출력이 끝납니다 a.


문자열 예

문자열이 같은 예는 동일한 핵심 차이점을 기반으로하며 final변수는 상수 표현식 / 변수로 처리되며 effectively final그렇지 않습니다.

Java에서 문자열 인턴 은 상수 표현식을 기반으로하므로

"a" + "b" + "c" == "abc"

이다 true(실제 코드에서이 구문을 사용 해달라고)뿐만 아니라.

JLS§3.10.5 참조 :

또한 문자열 리터럴은 항상 String 클래스의 동일한 인스턴스를 참조합니다. 이는 문자열 리터럴 또는 보다 일반적으로 상수 표현식 ( §15.29 ) 의 값인 문자열이 메서드 ( §12.5 )를 사용하여 고유 한 인스턴스를 공유하도록 " 인터 닝 " 되기 때문 입니다.String.intern

주로 리터럴에 대해 이야기하기 때문에 간과하기 쉽지만 실제로는 상수 표현에도 적용됩니다.


8
문제는 당신이 기대하는 것입니다 ... ? a : 'c'여부 같은 행동 aA는 변수상수를 . 표현에 명백히 잘못된 것은 없습니다. --- 대조적으로, a + "b" == "ab"A는 나쁜 표현 문자열의 요구를 사용하여 비교 할 수 있기 때문에, equals()( 나는 자바에서 문자열을 비교하려면 어떻게? ). 때 "실수로"작동한다는 사실 aA는 상수 , 문자열 리터럴의 인턴 단지 특질이다.
Andreas

5
@Andreas 예,하지만 문자열 인턴 은 Java의 명확하게 정의 된 기능입니다. 내일이나 다른 JVM에서 변경 될 수있는 우연이 아닙니다. 유효한 Java 구현에 "a" + "b" + "c" == "abc"있어야합니다 true.
Zabuzard

10
사실, 잘 정의 된 기이하지만 a + "b" == "ab"여전히 잘못된 표현 입니다. 당신이 경우에도 알고aA는 상수 , 너무 오류가 발생하기 쉬운 전화하지 않는 것입니다 equals(). 또는 fragile 이 더 나은 단어 일 수 있습니다. 즉, 코드가 앞으로 유지 될 때 무너질 가능성이 너무 높습니다.
Andreas

2
효과적인 최종 변수의 기본 도메인 (예 : 람다 식에서 사용)에서도 차이는 런타임 동작을 변경할 수 있습니다. , 그러나 전자는 새로운 객체를 생성합니다. 즉, 가 (not) 일 (final) String str = "a"; Stream.of(null, null). <Runnable>map( x -> () -> System.out.println(str)) .reduce((a,b) -> () -> System.out.println(a == b)) .ifPresent(Runnable::run);때 결과를 변경합니다 . strfinal
Holger 2016 년

7

또 다른 측면은 변수가 메서드 본문에서 final로 선언되면 매개 변수로 전달 된 최종 변수와 다른 동작을한다는 것입니다.

public void testFinalParameters(final String a, final String b) {
  System.out.println(a + b == "ab");
}

...
testFinalParameters("a", "b"); // Prints false

동안

public void testFinalVariable() {
   final String a = "a";
   final String b = "b";
   System.out.println(a + b == "ab");  // Prints true
}

...
testFinalVariable();

컴파일러가 사용하는 것을 알고 있기 때문에 발생 변수 것은 항상있을 것이다 있도록 값을 하고 문제없이 교환 할 수있다. 다르게 정의되지 않았 거나 정의 되었지만 런타임에 값이 할당 된 경우 (final이 매개 변수 인 위의 예에서와 같이 ) 컴파일러는 사용하기 전에 아무것도 알지 못합니다. 따라서 연결은 런타임에 발생하고 인턴 풀을 사용하지 않고 새 문자열이 생성됩니다.final String a = "a"a"a"a"a"afinalfinala


기본적으로 동작은 다음과 같습니다. 컴파일러가 변수가 상수라는 것을 알고 있으면 상수를 사용하는 것과 동일하게 사용할 수 있습니다.

변수가 final로 정의되지 않았거나 변수가 final이지만 런타임에 값이 정의 된 경우 컴파일러는 값이 상수와 같고 값이 변경되지 않는 경우에도 상수로 처리 할 이유가 없습니다.


4
거기에 이상한 것은 없습니다 :)
dbl

2
질문의 또 다른 측면입니다.
Davide Lorenzo MARINO

5
finel매개 변수에 적용된 키워드 final, 지역 변수에 적용된 것과 다른 의미 등 ...
dbl

6
여기서 매개 변수를 사용하는 것은 불필요하게 난독 화됩니다. 당신은 할 수 final String a; a = "a";와 같은 동작을 얻을
yawkat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.