development

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

big-blog 2020. 3. 3. 23:01
반응형

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


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

( 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는 것으로 알려져 캡처 로컬 파라미터의 값. 매개 변수가 최종 (또는 효과적으로 최종)이 아닌 경우 값이 변경되어 캡처 된 값과 동기화되지 않아 직관적이지 않은 동작이 발생할 수 있습니다.


익명의 내부 클래스 내의 메소드는 생성 된 스레드가 종료 된 후에 호출 될 수 있습니다. 귀하의 예제에서 내부 클래스는 이벤트 발송 스레드에서 호출되며 생성 된 스레드와 동일한 스레드가 아닙니다. 따라서 변수의 범위가 달라집니다. 따라서 이러한 변수 할당 범위 문제를 보호하려면이를 최종적으로 선언해야합니다.


익명의 내부 클래스가 메소드 본문 내에 정의되면 해당 메소드의 범위에서 final로 선언 된 모든 변수는 내부 클래스에서 액세스 할 수 있습니다. 스칼라 값의 경우, 일단 지정되면 최종 변수의 값을 변경할 수 없습니다. 객체 값의 경우 참조를 변경할 수 없습니다. 이를 통해 Java 컴파일러는 런타임시 변수 값을 "캡처"하고 사본을 내부 클래스의 필드로 저장할 수 있습니다. 외부 메소드가 종료되고 스택 프레임이 제거되면 원래 변수는 사라지지만 내부 클래스의 개인 사본은 클래스의 자체 메모리에 유지됩니다.

( http://en.wikipedia.org/wiki/Final_%28Java%29 )


private void f(Button b, final int a[]) {

    b.addClickHandler(new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {
            a[0] = a[0] * 5;

        }
    });
}

마찬가지로 존은 이 구현 세부 사항은 다른 가능한 대답은 JVM이 자신의 활성화를 종료 한 레코드 쓰기를 처리하지 않는 것이 답합니다.

적용되는 대신 람다가 어느 곳에 저장되어 나중에 실행되는 유스 케이스를 고려하십시오.

스몰 토크에서는 그러한 수정을하면 불법 상점이 생겼다는 것을 기억합니다.


이 코드를 사용해보십시오

배열 목록을 만들고 그 안에 값을 넣고 그것을 반환하십시오 :

private ArrayList f(Button b, final int a)
{
    final ArrayList al = new ArrayList();
    b.addClickHandler(new ClickHandler() {

         @Override
        public void onClick(ClickEvent event) {
             int b = a*5;
             al.add(b);
        }
    });
    return al;
}

어쩌면이 트릭은 당신에게 아이디어를 줄 것입니다.

Boolean var= new anonymousClass(){
    private String myVar; //String for example
    @Overriden public Boolean method(int i){
          //use myVar and i
    }
    public String setVar(String var){myVar=var; return this;} //Returns self instane
}.setVar("Hello").method(3);

참고 URL : https://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class



반응형