최종과 실제 최종의 차이점


351

Java 8에서 람다를 가지고 놀고 있는데 경고가 나왔습니다 local variables referenced from a lambda expression must be final or effectively final. 익명 클래스에서 변수를 사용할 때 외부 클래스에서 최종 변수 여야하지만 여전히 final효과적으로 final 의 차이점은 무엇 입니까?


2
많은 답변이 있지만 본질적으로 "차이가 없습니다". 그러나 이것이 사실입니까? 불행히도 Java 8에 대한 언어 사양을 찾을 수없는 것 같습니다.
Aleksandr Dubinsky

3
@AleksandrDubinsky의 docs.oracle.com/javase/specs
EIS

@AleksandrDubinsky는 "정말"사실이 아닙니다. 이 규칙에서 하나의 예외를 발견했습니다. 상수로 초기화 된 로컬 변수는 컴파일러에 대한 상수 표현식이 아닙니다. 최종 키워드를 명시 적으로 추가 할 때까지 스위치 / 케이스에서 사례에 이러한 변수를 사용할 수 없습니다. 예 : "int k = 1; switch (someInt) {case k : ...".
Henno Vermeulen

답변:


234

... Java SE 8부터 로컬 클래스는 최종 또는 효과적으로 최종 블록의 로컬 변수 및 매개 변수에 액세스 할 수 있습니다. 초기화 후에 값이 변경되지 않는 변수 또는 매개 변수는 사실상 최종입니다.

예를 들어 변수 numberLength가 final로 선언되지 않고 PhoneNumber생성자 에 표시된 대 입문을 추가 한다고 가정 하십시오 .

public class OutterClass {  

  int numberLength; // <== not *final*

  class PhoneNumber {

    PhoneNumber(String phoneNumber) {
        numberLength = 7;   // <== assignment to numberLength
        String currentNumber = phoneNumber.replaceAll(
            regularExpression, "");
        if (currentNumber.length() == numberLength)
            formattedPhoneNumber = currentNumber;
        else
            formattedPhoneNumber = null;
     }

  ...

  }

...

}

이 대 입문 때문에 변수 numberLength는 더 이상 최종적으로 유효하지 않습니다. 결과적으로 Java 컴파일러 는 내부 클래스 PhoneNumber가 numberLength 변수에 액세스하려고 시도하는 "내부 클래스에서 참조 된 로컬 변수는 최종적이거나 효과적으로 최종적이어야합니다"와 유사한 오류 메시지를 생성 합니다.

http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html

http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html


68
+1 참고 : 참조가 변경되지 않으면 참조 된 개체가 변경 되더라도 사실상 최종입니다.
피터 로리

1
@stanleyerror 이것은 도움이 될 것입니다 : stackoverflow.com/questions/4732544/…

1
나는 효과적으로 최종적 이지 않은 예보다 더 유용하다고 생각합니다 . 무언가 실제로 최종적인 예입니다 . 설명은 명확하게하지만. 코드가 값을 변경하지 않으면 Var를 final로 선언 할 필요가 없습니다.
Skychan

1
예가 잘못되었습니다. 이 코드는 물론 점없이 완벽하게 컴파일됩니다. 컴파일러 오류를 얻으려면이 코드가 일부 메소드 내에 있어야 하므로이 메소드 numberLength의 로컬 변수가됩니다.
mykola

1
이 예제가 그렇게 복잡한 이유가 있습니까? 코드의 대부분이 전적으로 관련없는 정규식 작업을 처리하는 이유는 무엇입니까? 그리고 @mykola가 이미 말했듯이 유효 최종 속성 에 대한 마크는 완전히 누락되었습니다. 이는 로컬 변수에만 관련이 있으며이 예제에는 로컬 변수가 없기 때문입니다.
Holger

131

"효과적으로 최종"을 설명하는 가장 간단한 방법 final은 변수 선언에 수정자를 추가하는 것을 상상하는 것 입니다. 이 변경으로 인해 프로그램이 컴파일 타임과 런타임 모두에서 동일한 방식으로 계속 작동하면 해당 변수는 사실상 최종입니다.


4
이것은 java 8의 "final"에 대한 이해가 잘 이해되어있는 한 사실입니다. 그렇지 않으면 나는 final로 선언되지 않은 변수를보고 나중에 할당 한 것이 잘못되어 최종 변수가 아니라고 잘못 생각합니다. "물론"이라고 말할 수도 있지만 모든 사람이 최신 언어 버전 변경에 관심을 기울이는 것은 아닙니다.
fool4jesus

8
이 규칙의 한 가지 예외는 상수로 초기화 된 로컬 변수가 컴파일러에 대한 상수 표현식이 아니라는 것입니다. 최종 키워드를 명시 적으로 추가 할 때까지 스위치 / 케이스에서 사례에 이러한 변수를 사용할 수 없습니다. 예 : "int k = 1; switch (someInt) {case k : ...".
Henno Vermeulen

2
@HennoVermeulen 스위치 케이스는이 답변의 규칙에 예외가 아닙니다. 언어 는 상수 변수 가 될 수 case k있는 상수 표현식 을 요구하도록 지정합니다 ( "상수 변수는 기본 표현식 또는 상수 문자열로 초기화되는 유형 문자열의 최종 변수" JLS 4.12.4 ). 변하기 쉬운.
Colin D Bennett

3
내 예제에서 컴파일러는 k가 상수 표현식이 아니므로 스위치에 사용할 수 없다고 불평합니다. final을 추가 할 때 컴파일 동작은 이제 상수 변수이므로 스위치에서 사용할 수 있기 때문에 변경됩니다. 따라서 당신은 옳습니다 : 규칙은 여전히 ​​옳습니다. 이 예제에는 적용되지 않으며 k가 실제로 최종적인지 여부를 말하지 않습니다.
Henno Vermeulen

36

문서 에 따르면 :

초기화 후에 값이 변경되지 않는 변수 또는 매개 변수는 사실상 최종입니다.

기본적으로 컴파일러가 초기화 외부의 할당에 변수가 나타나지 않으면 변수는 효과적으로 final 로 간주됩니다 .

예를 들어 다음과 같은 클래스를 고려하십시오.

public class Foo {

    public void baz(int bar) {
        // While the next line is commented, bar is effectively final
        // and while it is uncommented, the assignment means it is not
        // effectively final.

        // bar = 2;
    }
}

문서는 지역 변수에 대해 이야기합니다. bar귀하의 예에서 지역 가변형이 아니라 필드입니다. 위와 같은 오류 메시지의 "효과적으로 최종"은 필드에 전혀 적용되지 않습니다.
Antti Haapala

6
@AnttiHaapala bar는 필드가 아닌 매개 변수입니다.
peter.petrov

30

'Effectively final'은 'final'에 의해 추가 될 경우 컴파일러 오류를 발생시키지 않는 변수입니다.

'Brian Goetz'의 기사에서

비공식적으로, 로컬 변수는 초기 값이 변경되지 않으면 사실상 최종입니다. 즉, 최종 선언하면 컴파일 실패가 발생하지 않습니다.

람다 국가 결승전 브라이언 괴츠


2
이 답변이 확실하지 않은 단어에 대한,하지만 브라이언의 기사에서 이러한 정확한 텍스트가 없습니다, 견적으로 표시됩니다 추가 . :이 견적 대신이다 - 즉, 그것은 최종 컴파일 오류가 발생할하지 않을 선언 비공식적으로는, 지역 변수는 초기 값이 변경되지 않습니다 경우 효과적으로 마지막이다.
lcfd

기사 그대로의 사본에서 : 비공식적으로, 로컬 변수는 초기 값이 변경되지 않으면 사실상 최종입니다. 즉, 최종 선언하면 컴파일 실패가 발생하지 않습니다.
Ajeet Ganga

26

아래의이 변수는 final 이므로 초기화되면 값을 변경할 수 없습니다. 우리가 시도하면 컴파일 오류가 발생합니다 ...

final int variable = 123;

그러나 이와 같은 변수를 만들면 값을 변경할 수 있습니다.

int variable = 123;
variable = 456;

그러나 Java 8 에서는 모든 변수가 기본적으로 최종 변수 입니다. 그러나 코드에 두 번째 줄이 있으면 최종적이지 않습니다 . 따라서 위 코드에서 두 번째 줄을 제거하면 변수가 "효과적으로 최종"입니다 .

int variable = 123;

그래서 .. 한 번만 할당 된 모든 변수는, "효과적으로 최종"입니다 .


대답은 간단해야합니다.
superigno

@Eurig, "모든 변수는 기본적으로 최종"인 인용이 필요합니다.
Pacerier

10

변수는 최종 또는 효과적으로 마지막그것이 한 번 초기화 된 것 과는 적도 돌연변이하지 소유자 클래스. 그리고 우리는 초기화 할 수 없습니다 그것을 루프 또는 내부 클래스 .

최종 :

final int number;
number = 23;

효과적으로 최종 :

int number;
number = 34;

참고 : FinalEffective Final 은 비슷하지만 (지정 후 값이 변경되지 않음) 효과적인 Final 변수는 Keyword로 선언되지 않습니다 final.


7

람다식이 둘러싸는 공간에서 할당 된 로컬 변수를 사용하는 경우 중요한 제한이 있습니다. 람다 식은 값이 변경되지 않는 로컬 변수 만 사용할 수 있습니다. 이러한 제한을 " 변수 캡처 " 라고 합니다 . 변수가 아닌 람다 식 캡처 값 .
람다식이 사용할 수있는 지역 변수를 " 실제로 최종 "이라고합니다.
효과적으로 최종 변수는 처음 지정된 후에 값이 변경되지 않는 변수입니다. 이러한 변수를 final로 명시 적으로 선언 할 필요는 없지만 오류는 아닙니다.
예를 들어 보자. 우리는 지역 변수 i를 값 7로 초기화했다. 람다 식에서 우리는 i에 새로운 값을 할당하여 그 값을 변경하려고한다. 이로 인해 컴파일러 오류가 발생합니다. " 둘러싸는 범위에 정의 된 로컬 변수 i는 최종적이거나 효과적으로 최종적이어야합니다. "

@FunctionalInterface
interface IFuncInt {
    int func(int num1, int num2);
    public String toString();
}

public class LambdaVarDemo {

    public static void main(String[] args){             
        int i = 7;
        IFuncInt funcInt = (num1, num2) -> {
            i = num1 + num2;
            return i;
        };
    }   
}

2

효과적인 최종 주제는 JLS 4.12.4에 설명되어 있으며 마지막 단락은 명확한 설명으로 구성됩니다.

변수가 효과적으로 최종 변수 인 경우 최종 수정자를 선언에 추가하면 컴파일 타임 오류가 발생하지 않습니다. 반대로, 최종 수정자가 제거되면 유효한 프로그램에서 final로 선언 된 로컬 변수 또는 매개 변수가 효과적으로 최종이됩니다.


2

final 은 키워드로 선언 한 변수입니다 ( final예 :

final double pi = 3.14 ;

그것은 final프로그램을 통해 남아 있습니다.

효과적으로 final : 현재 한 번만 값이 할당되거나 한 번만 업데이트되는 로컬 변수 또는 매개 변수. 프로그램 전체에 걸쳐 최종적으로 유효 하지 않을 수 있습니다 . 따라서 효과적으로 최종 변수는 최소한 하나 이상의 할당을 할당 / 업데이트 한 후 즉시 최종 속성을 잃을 수 있습니다. 예:

class EffectivelyFinal {

    public static void main(String[] args) {
        calculate(124,53);
    }

    public static void calculate( int operand1, int operand2){   
     int rem = 0;  //   operand1, operand2 and rem are effectively final here
     rem = operand1%2  // rem lost its effectively final property here because it gets its second assignment 
                       // operand1, operand2 are still effectively final here 
        class operators{

            void setNum(){
                operand1 =   operand2%2;  // operand1 lost its effectively final property here because it gets its second assignment
            }

            int add(){
                return rem + operand2;  // does not compile because rem is not effectively final
            }
            int multiply(){
                return rem * operand1;  // does not compile because both rem and operand1 are not effectively final
            }
        }   
   }    
}

Java 언어 사양에 따르면 이는 " 할당 표현식에서 왼쪽으로 나타날 때마다 할당이 지정되지 않았으며 할당 전에 지정되지 않은 것입니다." 변수 / 매개 변수는 항상 또는 절대적으로 절대적입니다. 보다 명확하게, final컴파일 오류를 발생시키지 않고 선언에 키워드를 추가 할 수 없으면 효과적으로 final 이 아닙니다 . 이 변수는 "변수가 효과적으로 최종 변수 인 경우 최종 수정자를 선언에 추가해도 컴파일 타임 오류가 발생하지 않습니다."라는 반대 문입니다.
AndrewF 2016 년

예제 코드의 주석은 내 주석에 설명 된 모든 이유로 올바르지 않습니다. "유효 최종"은 시간이지나면서 변할 수있는 상태가 아닙니다.
AndrewF 2016 년

@AndrewF 시간이 지나도 변경되지 않으면 마지막 줄이 어떻게 컴파일되지 않는다고 생각하십니까? rem은 계산 방법에서 1 행에서 실제로 최종적이었습니다. 그러나, 마지막 줄에, 컴파일러는 REM 효과적으로 최종 아니라고 불평
과학적인 방법

컴파일하려면 코드 블록에서 일부 코드를 제거해야하지만 런타임 동작은 반영하지 않는 것이 맞습니다. 컴파일 타임에 변수를 효과적으로 최종적인지 아닌지 결정할 수 있습니다. 스펙에 따라, 항상 효과적으로 최종적인 것인지, 아니면 절대로 최종적인 것인지는 결정할 수 없습니다 . 컴파일러는 변수가 해당 범위에서 어떻게 사용되는지 정적으로보고 알 수 있습니다. 프로그램이 실행되면 속성을 얻거나 잃을 수 없습니다. 이 용어는 사양에 의해 잘 정의되어 있습니다. 다른 답변을 확인하십시오.
AndrewF 2016 년

1
public class LambdaScopeTest {
    public int x = 0;        
    class FirstLevel {
        public int x = 1;    
        void methodInFirstLevel(int x) {

            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99; 

        }
    }    
}

다른 사람들이 말했듯이, 초기화 된 후에 값이 변경되지 않는 변수 또는 매개 변수는 사실상 최종입니다. 위의 코드 x에서 내부 클래스 의 값을 변경 FirstLevel하면 컴파일러에서 오류 메시지를 표시합니다.

람다 식에서 참조되는 지역 변수는 최종적이거나 효과적으로 최종적이어야합니다.


1

final수정자를 지역 변수에 추가 할 수 있다면 사실상 최종 변수 입니다.

람다 식에 액세스 할 수 있습니다

  • 정적 변수

  • 인스턴스 변수

  • 효과적으로 최종 분석법 파라미터

  • 효과적으로 최종 지역 변수.

출처 : OCP : Oracle Certified Professional Java SE 8 Programmer II Study Guide, Jeanne Boyarsky, Scott Selikoff

또한

effectively final변수는 값이 변경되지 않습니다 변수이지만,이 선언되지 않은 final키워드.

출처 : Java로 시작하기 : 제어 구조에서 객체 (6 판)까지, Tony Gaddis

또한 final처음 사용하기 전에 정확히 한 번 초기화된다는 의미를 잊지 마십시오 .


0

변수를 선언 final하거나 선언하지 final않지만 효과적으로 최종적으로 유지하면 다른 바이트 코드로 (컴파일러에 따라) 발생할 수 있습니다.

작은 예를 살펴 보겠습니다.

    public static void main(String[] args) {
        final boolean i = true;   // 6  // final by declaration
        boolean j = true;         // 7  // effectively final

        if (i) {                  // 9
            System.out.println(i);// 10
        }
        if (!i) {                 // 12
            System.out.println(i);// 13
        }
        if (j) {                  // 15
            System.out.println(j);// 16
        }
        if (!j) {                 // 18
            System.out.println(j);// 19
        }
    }

main메소드 의 해당 바이트 코드 (Windows 64 비트의 Java 8u161) :

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_1
       3: istore_2
       4: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       7: iconst_1
       8: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      11: iload_2
      12: ifeq          22
      15: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      18: iload_2
      19: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      22: iload_2
      23: ifne          33
      26: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      29: iload_2
      30: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      33: return

해당 라인 번호 테이블 :

 LineNumberTable:
   line 6: 0
   line 7: 2
   line 10: 4
   line 15: 11
   line 16: 15
   line 18: 22
   line 19: 26
   line 21: 33

우리가 라인에서 소스 코드를 참조로 12, 13, 14바이트 코드에 표시되지 않습니다. 그 이유 itrue상태이므로 변경하지 않을 것입니다. 따라서이 코드는 도달 할 수 없습니다 (이 답변 에서 더 ). 같은 이유로 줄의 코드 9도 그리워합니다. 의 상태는 확실 i하므로 평가할 필요가 없습니다 true.

반면 변수 j사실상 최종 변수이지만 같은 방식으로 처리되지 않습니다. 적용된 최적화는 없습니다. 의 상태 j는 두 번 평가됩니다. 바이트 코드는 실제로 최종적인j 것에 관계없이 동일 합니다.


나는 이것이 컴파일러의 비 효율성이라고 생각할 것이며, 반드시 새로운 컴파일러에서 여전히 유효한 것은 아닙니다. 완벽한 컴파일에서 변수가 효과적으로 final이면 선언 된 final과 동일한 최적화가 모두 생성됩니다. 따라서 final을 효과적으로 선언하는 것이 final을 선언하는 것보다 자동으로 느리다는 개념에 의존하지 마십시오.
AndrewF

@AndrewF 일반적으로 당신이 맞습니다, 행동이 바뀔 수 있습니다. 그래서 나는 " 다른 바이트 코드로 결과가 나올 수있다 " (컴파일러에 따라 다름) 라고 썼다 . 누락 된 최적화 (다른 바이트 코드) 때문에 실행이 느리다고 가정하지는 않습니다. 그러나 그것은 여전히 ​​제시된 경우의 차이입니다.
LuCio

0

효과적으로 최종 변수는 다음과 같은 지역 변수입니다.

  1. 로 정의되지 않음 final
  2. 한 번만 할당되었습니다.

최종 변수는 다음과 같은 변수입니다.

  1. final키워드로 선언했습니다 .

-6

그러나 Java SE 8부터 로컬 클래스는 최종 또는 효과적으로 최종 블록의 로컬 변수 및 매개 변수에 액세스 할 수 있습니다.

이것은 Java 8에서 시작되지 않았으므로 오랫동안 사용했습니다. 이 코드는 (Java 8 이전) 합법적이었습니다.

String str = ""; //<-- not accesible from anonymous classes implementation
final String strFin = ""; //<-- accesible 
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
         String ann = str; // <---- error, must be final (IDE's gives the hint);
         String ann = strFin; // <---- legal;
         String str = "legal statement on java 7,"
                +"Java 8 doesn't allow this, it thinks that I'm trying to use the str declared before the anonymous impl."; 
         //we are forced to use another name than str
    }
);

2
성명은 자바 8 <에 있다는 사실을 의미 만을 final 변수에 액세스 할 수 있지만 자바 8 그 것을 효과적으로 마지막.
Antti Haapala

Java 7 또는 Java 8을 사용하는지 여부에 관계없이 작동하지 않는 코드 만 볼 수 있습니다.
Holger
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.