Java

10. 중첩 클래스

문정훈 2022. 2. 6. 17:30

0. 도입

중첩 클래스란 클래스 내부에 선언된 클래스를 말한다. 

중첩 클래스를 사용하면 외부 클래스와 내부 클래스의 맴버 간의 접근 용이성과 외부에 불필요한 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다. 

자바에서 중첩 클래스에는 아래와 같이 분류된다. 

 

  1. 맴버 클래스
    1) 인스턴스 맴버 클래스
    2) 정적 맴버 클래스
  2. 로컬 클래스

 

1. 맴버 클래스

1) 인스턴스 맴버 클래스

class A {
	class B {
            B() {...}
            int a;
            void func() {...}

            //static 선언 안됨 
            //static int b; 
            //static void func1(){...}
    }
	
}

위 예시에서 중첩 클래스는 B가 된다. B는 A의 인스턴트 맴버로써 존재하기 때문에 중첩 클래스가 인스턴스 맴버인 경우가 된다.

B내부에서는 static 타입의 맴버 선언이 불가능한데 그 이유는 A라는 객체가 생성되지도 않았는데 A 클래스 내부의 B 클래스 내부의 static 맴버에 접근하는 것을 불가능하기 때문이다. 

 

 

 

2) 정적 맴버 클래스

class A{
      static classB{
         B(){}
         int a;
         void func(){}
         static int b; //됨
         static void func1(){ }//됨
	}
}
A.B b = new A.B();
b.a = 10;
b.func();
A.B.b = 20;
A.B.fucn1();

정적 맴버 클래스로 사용되는 경우는 A라는 클래스의 중첩 클래스로 B가 선언되어 있는데 B가 A의 정적 맴버로써 선언되었기 때문에 해당 B 중첩 클래스는 정적 맴버 클래스가 된다. 

정적 맴버 클래스 내부에서는 static 타입은 물론 인스턴스 타입의 맴버 모두 생성이 가능하다.

중첩 클래스(정적 맴버) 내부의 static 타입은 A.B.b = 10; 과 같이 객체를 생성하지 않고 바로 접근이 가능하지만 

B 내부의 a맴버와 같이 클래스 맴버를 사용하려는 경우에는 A.B b = new A.B(); 이와 같이 중첩 클래스의 객체를 생성시킨 뒤 접근해야한다. 


 

 

2. 로컬 클래스

메소드 내에 클래스를 선언한 것을 로컬 클래스라고 한다. 

로컬클래스는 그 메소드 내부에서 사용할 클래스를 선언하는 것이라 바깥 클래스에서 로컬 클래스를 사용할 수는 없다.

public class A {
	public void func1()
	{
		 class B{
			 int a;
			 void func() {}
			// static int b; 안됨
			// static void func1() {}; 안됨
		}
	}

}

로컬 클래스 내부에서는 static 맴버 선언이 불가능하다.


 

 

 

※ 탐구 : static 메소드 내부의 로컬 클래스

class A {
    int a1;
    static int a2;
    
    static void method() {
    	class B {
            void func() {
            	//a1 = 10; 1번코드
                a2 = 10;
            }
            
            int b1;
            //static int b2; 2번코드
        }
        
        B b = new B();
        
        //static int c = 10; 3번 코드
    }
}

 

1) 1번 코드를 보면 a1의 사용이 안되는 이유는 정적 메소드 method 내부가 실행될 때 메소드는 static 타입이므로 외부 클래스인 A의 객체 생성 없이 method를 실행한다. 따라서 A의 객체가 없으므로 method 내부에서 인스턴스 타입 맴버인 a1에 접근은 안된다. 

 

 

2) 2번 코드를 보면 로컬 클래스를 선언한 메소드가 정적 메소드이던 인스턴스 타입의 메소드이던 로컬 클래스 내부에서 static 타입의 맴버 선언은 불가능함.

 

3) 3번  코드는 당연히 안되는데 정적 메소드라할지라도 메소드 내부에서는 로컬 변수가 사용되는데 로컬 변수에는 static 사용이 안된다. 


 

 

 

3.  중첩클래스의 접근 제한

public class A {
    B filed1 = new B();
    C filed2 = new C();
    
    class B {} //인스턴트 맴버 중첩 클래스 
    static class C {} //정적 맴버 중첩 클래스
    
    //static B filed3 = new B(); 1번
    static C filed4 = new C();
    
    void method1() {
    	B var1 = new B();
        C var2 = new C();
    }
    
    static void method2() {
    	//B var1 = new B(); 2번
        C var2 = new C();
    }
    
}

<1번 코드 설명>

A라는 클래스 내부에는 맴버 타입의 중첩 클래스 B, static 타입의 맴버인 중첩 클래스 C가 존재한다. 

C는 static 맴버이지만 클래스 이므로 객체 생성이 가능하다. new C(); 가 가능

클래스 A의 정적 맴버로 B 객체를 생성하는 것은 불가능하다. 그 이유는

A라는 클래스가 컴파일 되면 맴버보다 static 맴버, static 필드, 메소드들부터 먼저 처리된다. 

즉 static B filed3 = new B();를 실행할 때 B라는 맴버는 (클래스)는 아직 존재하지 않는다. 따라서 이는 잘못된 표현이다. 

 

예시)

public class A {
 int a = 10;
 
 static void func() {
 	System.out.println(a);// 오류
 }
}

1번 코드의 오류는 위 예시와 똑같은 논리의 오류이다.  

 

 

<2번 코드 설명>

2번 코드가 왜 오류냐면 A클래스의 정적 메소드에서 A 클래스의 인스턴트 맴버에 접근하려하기 때문에 오류인 것이다. 

위 예시와 똑같은 짓을 하는 거임.


 

 

 

4. 로컬 클래스에서 final변수

- 매개변수, 매소드 안에 로컬 변수가 있다. 만약 메소드 안에 로컬 클래스가 선언되 있다고 하면  fianl키워드가 있는 매개변수, 로컬 변수는 로컬 클래스의 모든 매소드의 지역 변수로 들어가게 되고 fianl키워드가 없는 매개변수, 로컬 변수는 로컬클래스의 필드로 복사 된다. 


- 로컬 클래스는 메소드(m이라고하면) 안에 로컬 변수의 값에 접근할 수 있다.

이때 접근되는 로컬 변수들은 final특성을 가지게 된다. 따라서 해다 로컬 변수는 값을 변경하지 못하게 된며 값을 변경하려할 시 오류가 발생한다.  아래 예시 코드를 보면

class A {
	public void method() {
    	int a1 = 10;
        final int a2 = 20;
        int a3 = 30;
        final int a4 = 40;
        
        class B {
          //int b1 = a1;  이 코드는 오류가 발생한다. 
            int b2 = a2;
        }
        
        a1 = -1;
        a3 = -1;
    }
}

우선 메소드의 final 로컬 변수인 a2, a4는 로컬 클래스에서 사용된다면 로컬 클래스의 모든 메소드의 지역 변수로 복사된다. 그리고 final의 특성을 가지게 되어 method 내부에서 a2, a4의 값을 변경하지 못한다. (애초에 final이어서 값 변경 못함)

 

그리고 a1, a2 지역 변수는 로컬 클래스에서 사용된다면 로컬 클래스의 필드로 복사되는데 이때 복사된 필드는 final의 특성을 가지게 된다.

따라서 위 예시 코드를 보면 a2라는 method의 지역 변수가 로컬 클래스에서 사용되므로 final의 특성을 지니게 되는데

a1의 값을 a1 = -1; 로 변경하려는 시도가 있기 떄문에 오류가 발생하게 된다 .

반면 a3의 값은 로컬 클래스 내부에서 사용되지 않으므로 final 특성이 아니다. 

 

이와 같이 메소드의 지역 변수가 로컬 클래스에서 사용될 때 final 속성이 되는 이유는 

메소드는 종료되지만 메소드 내부의 로컬 클래스의 객체는 살아있을 수 있기 떄문에 메소드의 지역변수를 로컬 클래스 내부로 복사하는 것이며 메소드 내부에서 값을 변경한다면 클래스 내부에서 해당 변수의 값이 옳바르게 동작하지 못하기 때문이다. 


 

 

5. 중첩 클래스에서 바깥 클래스의 참조 얻는 방법

[바깥클래스].this.[필드]
[바깥클래스].this.[메소드]

'Java' 카테고리의 다른 글

12. 자바 API 클래스  (0) 2022.02.17
11. 자바 예외 처리(실행 예외, 예외 처리 코드, 사용자 정의 예외)  (0) 2022.02.16
9. 인터페이스  (0) 2022.02.06
8. 상속  (0) 2022.02.06
7. 클래스 탐구  (0) 2022.01.20