[java] 익명 클래스에서 최종 변수에만 액세스 할 수있는 이유는 무엇입니까?

  1. a여기서 만 최종적 일 수 있습니다. 왜? 어떻게 재 할당 할 수 있습니다 aonClick()개인 회원으로 그것을 유지하지 않고 방법은?

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                int b = a*5;
    
            }
        });
    }
    
  2. 5 * a클릭했을 때 어떻게 반환 합니까? 내말은,

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                 int b = a*5;
                 return b; // but return type is void 
            }
        });
    }
    


답변

주석에서 언급했듯이, 일부는 Java 8에서는 관련이 final없으며 암시적일 수 있습니다. 익명의 내부 클래스 또는 람다 식 에는 효과적으로 최종 변수 만 사용할 수 있습니다.


기본적으로 Java가 클로저를 관리하는 방식 때문 입니다.

익명 내부 클래스의 인스턴스를 만들면 해당 클래스 내에서 사용되는 모든 변수 는 자동 생성 생성자를 통해 값이 복사됩니다. 이렇게하면 컴파일러가 “로컬 변수”의 논리적 상태를 유지하기 위해 다양한 추가 유형을 자동 생성하지 않아도됩니다. 예를 들어 C # 컴파일러는 다음과 같습니다. (C #은 익명 함수에서 변수를 캡처 할 때 실제로 변수를 캡처합니다. 클로저는 메소드의 본문에서 볼 수있는 방식으로 변수를 업데이트 할 수 있습니다.

값이 익명 내부 클래스의 인스턴스에 복사되었으므로 변수가 나머지 메소드로 수정 될 수 있으면 이상하게 보일 것입니다. 오래된 변수로 작동하는 것으로 보이는 코드가있을 수 있습니다 ( 그 효과적으로 무엇 때문에 일이있을 … 당신이 다른 시간에 촬영 한 사본)와 함께 일하게 될 것입니다. 마찬가지로 익명의 내부 클래스 내에서 변경을 수행 할 수있는 경우 개발자는 해당 변경 사항이 엔 클로징 메서드 본문 내에서 표시 될 것으로 기대할 수 있습니다.

변수를 final로 설정하면 이러한 모든 가능성이 제거됩니다. 값을 전혀 변경할 수 없기 때문에 이러한 변경이 표시 될지 걱정할 필요가 없습니다. 메소드와 익명의 내부 클래스가 서로의 변경 사항을 볼 수있게하는 유일한 방법은 변경 가능한 유형의 설명을 사용하는 것입니다. 이것은 둘러싸는 클래스 자체, 배열, 변경 가능한 래퍼 유형 등이 될 수 있습니다. 기본적으로 그것은 한 메소드와 다른 메소드 사이의 통신과 비슷합니다. 한 메소드의 매개 변수 에 대한 변경 사항은 호출자 가 볼 수 없지만 매개 변수가 참조 하는 객체의 변경 사항 은 볼 수 있습니다.

Java와 C # 클로저의보다 자세한 비교에 관심이 있다면 더 자세히 다루는 기사 가 있습니다. 이 답변에서 Java 측에 집중하고 싶었습니다. 🙂


답변

익명 클래스가 외부 범위의 데이터를 업데이트 할 수있는 트릭이 있습니다.

private void f(Button b, final int a) {
    final int[] res = new int[1];
    b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            res[0] = a * 5;
        }
    });

    // But at this point handler is most likely not executed yet!
    // How should we now res[0] is ready?
}

그러나이 문제는 동기화 문제로 인해 그리 좋지 않습니다. 핸들러가 나중에 호출되는 경우 1) 다른 스레드에서 핸들러가 호출 된 경우 re에 대한 액세스를 동기화해야합니다. 2) res가 업데이트되었음을 ​​나타내는 일종의 플래그 또는 표시가 있어야합니다.

그러나 익명 클래스가 동일한 스레드에서 즉시 호출되면이 트릭은 정상적으로 작동합니다. 처럼:

// ...

final int[] res = new int[1];
Runnable r = new Runnable() { public void run() { res[0] = 123; } };
r.run();
System.out.println(res[0]);

// ...


답변

익명 클래스는 내부 클래스 이며 엄격한 규칙은 내부 클래스 (JLS 8.1.3)에 적용됩니다 .

내부 클래스에서 선언되었지만 선언되지 않은 로컬 변수, 공식 메서드 매개 변수 또는 예외 처리기 매개 변수 는 final로 선언해야합니다 . 내부 클래스에서 사용되었지만 선언되지 않은 로컬 변수는 내부 클래스 의 본문 앞에 반드시 지정해야합니다 .

jls 또는 jvms에 대한 이유 또는 설명을 아직 찾지 못했지만 컴파일러는 각 내부 클래스에 대해 별도의 클래스 파일을 작성하고이 클래스 파일에 선언 된 메소드가 있는지 확인해야합니다 ( 바이트 코드 수준에서)는 적어도 지역 변수의 값에 액세스 할 수 있습니다.

( Jon은 완전한 대답을 가지고 있습니다-JLS 규칙에 관심이있을 수 있으므로 삭제하지 마십시오)


답변

클래스 레벨 변수를 작성하여 리턴 값을 얻을 수 있습니다. 내말은

class A {
    int k = 0;
    private void f(Button b, int a){
        b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            k = a * 5;
        }
    });
}

이제 K의 가치를 얻고 원하는 곳에서 사용할 수 있습니다.

이유는 다음과 같습니다.

로컬 내부 클래스 인스턴스는 Main 클래스에 연결되며 포함하는 메서드의 최종 로컬 변수에 액세스 할 수 있습니다. 인스턴스가 포함하는 메소드의 최종 로컬을 사용하는 경우 변수가 범위를 벗어난 경우에도 변수는 인스턴스 작성시 보유한 값을 유지합니다 (이는 사실상 Java의 조잡하고 제한된 클로저 버전입니다).

로컬 내부 클래스는 클래스 또는 패키지의 멤버가 아니므로 액세스 수준으로 선언되지 않습니다. (단, 자신의 멤버는 일반 클래스와 같은 액세스 레벨을 갖습니다.)


답변

Java에서 변수는 매개 변수뿐만 아니라 클래스 수준 필드와 같이 최종 변수가 될 수 있습니다.

public class Test
{
 public final int a = 3;

또는 같은 지역 변수로

public static void main(String[] args)
{
 final int a = 3;

익명 클래스에서 변수에 액세스하고 변수를 수정하려면 해당 변수를 둘러싸는 클래스의 클래스 수준 변수 로 만들 수 있습니다 .

public class Test
{
 public int a;
 public void doSomething()
 {
  Runnable runnable =
   new Runnable()
   {
    public void run()
    {
     System.out.println(a);
     a = a+1;
    }
   };
 }
}

당신은 마지막으로 변수를 가질 수 없습니다 그리고 그것에게 새로운 가치를 제공합니다. final그 의미는 다음과 같습니다. 값이 변경 불가능하고 최종적입니다.

그리고 최종적으로 Java는 로컬 익명 클래스에 안전하게 복사 할 수 있습니다. int에 대한 참조 를 얻지 못합니다 (특히 Java의 int와 같은 프리미티브에 대한 참조를 가질 수 없으므로 Objects에 대한 참조 만 가능하기 때문에 ).

a의 값을 익명 클래스에서 a라는 암시 적 int로 복사합니다.


답변

액세스가 로컬 최종 변수로만 제한되는 이유는 모든 로컬 변수에 액세스 할 수있는 경우 먼저 내부 클래스가 액세스 할 수있는 별도의 섹션에 복사해야하고 여러 사본을 유지 보수해야하기 때문입니다. 가변 지역 변수는 데이터가 일치하지 않을 수 있습니다. 최종 변수는 불변이므로 변수에 대한 사본의 수는 데이터 일관성에 영향을 미치지 않습니다.


답변

이 제한의 이론적 근거를 이해하려면 다음 프로그램을 고려하십시오.

public class Program {

    interface Interface {
        public void printInteger();
    }
    static Interface interfaceInstance = null;

    static void initialize(int val) {
        class Impl implements Interface {
            @Override
            public void printInteger() {
                System.out.println(val);
            }
        }
        interfaceInstance = new Impl();
    }

    public static void main(String[] args) {
        initialize(12345);
        interfaceInstance.printInteger();
    }
}

interfaceInstance는 애프터 메모리에 남아 초기화 메소드가 리턴하지만, 매개 변수의 발은 하지 않습니다. JVM은 해당 범위 밖의 로컬 변수에 액세스 할 수 없으므로 Java는 val 값을 interfaceInstance 내에서 동일한 이름의 내재 된 필드 에 복사하여 printInteger에 대한 후속 호출을 수행합니다 . interfaceInstance는 것으로 알려져 캡처 로컬 파라미터의 값. 매개 변수가 최종 (또는 효과적으로 최종)이 아닌 경우 값이 변경되어 캡처 된 값과 동기화되지 않아 직관적이지 않은 동작이 발생할 수 있습니다.