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

LSM ·2022년 3월 5일
0

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개의 댓글