Java의 익명 내부 클래스의 변수 허용 범위!

LSM ·2022년 3월 5일

1. 익명 내부 클래스

익명 내부 클래스란? 이름이 없는 클래스의 일종이다!

클래스의 이름을 생략하고 주로 하나의 인터페이스나 하나의 추상 클래스를 구현하여 반환하는 형식이다!

  • 인터페이스나 추상 클래스 자료형의 변수에 직접 대입하여 클래스를 생성하거나, 지역 내부 클래스의 메서드 내부에서 생성하여 반환 할 수 있다.(추후 예제 코드로 확인하겠다)
  • 안드로이드 개발시에는, widget의 이벤트 핸들러에 주로 활용된다!

위 코드는 View 클래스의 내부 인터페이스인 OnClickListener 를 구현한, 익명 클래스를 반환하는 것을 보여준다!

추가로, 반환된 익명 클래스 내부의 onClick 메서드를 override 한 것이다.


2. 코드로 확인해보자

class Outer{

    int outNum = 100;
    static int sNum = 200;

    Runnable getRunnable(int i){

        int num = 100;

        class MyRunnable implements Runnable{

            int localNum = 10;

            @Override
            public void run() {
            		...
            }
        }
        return new MyRunnable();
    }
}

위 코드를 보면 getRunnable 메서드를 통해 Runnable을 구현한 MyRunnable을 반환한다.

그런데 사실 MyRunnable 클래스의 이름은 반환 이 외에 사용되지 않는다.

따라서 이를 익명으로 바꿀 수 있다.

Runnable 인터페이스를 구현한 익명 클래스 반환

class Outer2{

    int outNum = 100;
    static int sNum = 200;

    Runnable getRunnable(int i){ // 익명 내부 클래스
    
        int num = 100;
        
        return new Runnable(){

            @Override
            public void run() {
				...
        };
    }
}

MyRunnable 클래스의 이름을 익명으로 하여 반환할 수 있음을 확인했다!

추가적으로 java8에서 부터 도입된 람다식 역시 익명 내부 클래스로 구현되어진다.

자세한 코드 설명은 람다식 관련 글에서 확인할 수 있다.

그렇다면 이제는 해당 Outer2의 클래스 내부에 선언 된 변수의 허용범위에 대해 알아보겠다


2. 내부 클래스와 변수의 유효성

예제 코드) 위 예제인 Outer2를 다시 가져오겠다!

class Outer2 {

    int outNum = 100;
    static int sNum = 200;

    Runnable getRunnable(int i){ // 익명 내부 클래스

        int num = 100;

        return new Runnable(){

            int localNum = 10;

            @Override
            public void run() {
                System.out.println("i =" + i);
                System.out.println("num = " +num);
                System.out.println("localNum = " +localNum);

                System.out.println("outNum = " + outNum + "(외부 클래스 인스턴스 변수)");
                System.out.println("Outter.sNum = " + Outer.sNum + "(외부 클래스 정적 변수)");
            }
        };
    }
}

public class AnonymousInnerTest {

    public static void main(String[] args) {

        Outer2 out2 = new Outer2();
        Runnable runner2 = out.getRunnable(10);
        runner2.run();
    }

}

자! 코드를 한번 쓰윽~ 훑어본 후 한번 생각해보자!

  • getRunnable은 method이다. 즉 getRunnable method의 지역변수 및 매개변수는 stack 영역에 존재하게 되며, getRunnable method가 익명 클래스를 return 할 때 종료되며 stack 영역의 변수 유효성이 사라진다!

그렇다면??

			@Override
            public void run() {
                System.out.println("i =" + i);
                System.out.println("num = " +num);
                System.out.println("localNum = " +localNum);

                System.out.println("outNum = " + outNum + "(외부 클래스 인스턴스 변수)");
                System.out.println("Outter.sNum = " + Outer.sNum + "(외부 클래스 정적 변수)");
            }

그럼, 위 출력 부분의 getRunnable의 파라미터 i와, getRunnable의 지역변수 num을 출력하는 부분은 오류가 나야되는 게 정상이지 않을까??

하지만 위 코드에서 결과는 아래와 같이 정상적으로 나온다....!

어찌된 일인고.... 알아보니!

결론은!

메서드 종료 이후에도, 메서드 호출당시 받은 매개변수나 지역변수가 반환된 내부 클래스에서 사용해야 하는 경우가 있을 수 있으므로 지역 내부 클래스에서 사용하는 메서드의 지역 변수나 매개 변수는 final로 선언된다!!

호오,,,, 그렇다는 말은 즉

지역 내부 클래스에서 사용하는 메서드의 지역 변수나 매개 변수가 Stack영역이 아닌 상수영역 즉 Code영역에 저장이 된다.

따라서!

			@Override
			public void run() {
				//num = 200;   //에러 남. 지역변수는 상수로 바뀜
				//i = 100;     //에러 남. 매개 변수 역시 지역변수처럼 상수로 바뀜
				System.out.println("i =" + i); 
				System.out.println("num = " +num);  
				System.out.println("localNum = " +localNum);
					
				System.out.println("outNum = " + outNum + "(외부 클래스 인스턴스 변수)");
				System.out.println("Outter.sNum = " + Outer.sNum + "(외부 클래스 정적 변수)");
				}
			}

위 주석 처리한 부분을 주석 제거 하면, 에러가 난다.

이 글을 처음 부터 읽었다면 그 이유는 이제 자신있게 말할 수 있을 거라 생각한다 ㅎㅎ

정답은! Final로 선언되기 때문이다.

위와 같은 상황은 실제로 에러가 나면 왜 나는 지 잘 모를 수 있을 것 같다는 생각이들었다...

이번 글을 계기로 언어를 사용함에 있어 항상 기본에 충실하고, 개념적 이해가 필수 임을 알 수 있었다 !!

공부하면 할 수록 더 많이 보이는 것 같아 조금은 기쁘다 ㅎ


참고자료

한번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 Online. Part2/ch6-1

profile
개발 및 취준 일지

0개의 댓글