최종과 실제 최종의 차이점


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;

참고 : Final 및 Effective 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바이트 코드에 표시되지 않습니다. 그 이유 i는 true상태이므로 변경하지 않을 것입니다. 따라서이 코드는 도달 할 수 없습니다 (이 답변 에서 더 ). 같은 이유로 줄의 코드 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.