본문 바로가기

develop/Java

중첩 클래스

책에 따르면...

객체 지향 프로그래밍에서 클래스들은 서로 긴밀한 관계를 맺고 상호작용을 합니다.

어떤 클래스는 여러 클래스와 관계를 맺지만 어떤 클래스는 특정 클래스와 관계를 맺습니다. 

특정 클래스와 관계를 맺을 경우에는 클래스 내부에 선언하는 것이 좋습니다. 

 

 

중첩 클래스는 클래스 내부에 선언한 클래스이며 중첩클래스를 사용하면 두 클래스 멤버들을 

서로 쉽게 접근할 수 있고, 외부에는 불필요한 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있습니다. 

인터페이스 또한 마찬가지로 클래스 내부에 선언 가능하며 중첩 인터페이스라고 불림

=> 해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위해서 

 

중첩 클래스

 

중첩 클래스는 클래스 내부에 선언되는 위치에 따라 두 가지로 분류된다. 

1 - 클래스의 멤버로서 선언되는 중첩 클래스를 멤버 (내부) 클래스라고 하고

멤버 클래스는 인스턴스 멤버 클래스와 정적(static) 멤버 클래스로 나뉜다.

 

인스턴스 멤버 클래스

전자는 인스턴스 객체를 생성해야 사용할 수 있고(new) 클래스 A 안에 B가 선언되어 있다면

B클래스는 A의 전용클래스가 된다.

인스턴스 변수와 메서드만 선언이 가능하고 (생성자도 가능) 정적(static) 필드와 메서드는 선언할 수 없음. 

A a = new A();
A.B b = a.new B();
b.field1 = 3;
b.method();
class Otter1{ //외부클래스
	int outterMemberVariable = 99; //외부클래스의 인스턴스 멤버변수
    
    //1. 인스턴스 멤버 내부 클래스
    //=> 인스턴스 멤버변수와 동일한 위치에 정의하며, 동일한 사용법을 가진다
    
    class InstanceInnerClass{
    	int instanceMemberVariable = 10;
        
        public InstanceInnerClass(){} //생성자
        
        public void instanceMemberMethod(){ //인스턴스 멤버 메서드
        
        }
    
    }
    
    
    public void outterMethod(){
    	//멤버메서드에서는 인스턴스 내부 클래스에 자유롭게 접근 가능
        //=> 인스턴스 멤버변수처럼 접근(이름만으로도 가능)
        InstanceInnerClass iic = new InstanceInnerClass();
        iic.instanceMemberMethod();
    }

}

 

A 클래스 외부에서 호출할 경우 참조변수를 통해 a인스턴스를 a.new 로 해야함

 

정적 멤버 클래스

후자인 정적 멤버 클래스는 앞에 static이 붙었으므로 바로 접근이 가능함.

모든 종류의 필드와 메소드를 선언할 수 있다. 

 

class Outter2 { // 외부클래스
	static int outterStaticMemberVariable = 99; // 외부클래스의 클래스 멤버변수
	
	// 2. 클래스 멤버 내부 클래스(정적 멤버 클래스 = 정적 멤버 중첩 클래스)
	// => static 멤버변수와 동일한 위치에 정의하며, 동일한 사용법 가짐
	// => 워크스페이스 bin 폴더에 외부클래스명$내부클래스명.class 파일 생성됨
	static class StaticInnerClass {
		// static 멤버 및 생성자와 인스턴스 멤버를 모두 가짐
		int instanceMemberVariable = 10; // 인스턴스 멤버변수
		static int staticMemberVariable = 20; 
		
		public StaticInnerClass() {} // 생성자
		
		public static void staticMemberMethod() { // 인스턴스 멤버 메서드
			System.out.println("staticMemberMethod()");
		}
	}
	
	public void outterMethod() {
		System.out.println(outterStaticMemberVariable); // 외부클래스 멤버변수
		
		// 멤버메서드에서는 static 내부클래스에 자유롭게 접근 가능
		// => static 멤버변수처럼 접근(클래스명만으로 접근 가능)
		// 1) 인스턴스 생성 후 접근
		StaticInnerClass sic = new StaticInnerClass();
		System.out.println(sic.staticMemberVariable);
		sic.staticMemberMethod();
		
		// 2) 클래스명만으로 접근
		System.out.println(StaticInnerClass.staticMemberVariable);
		StaticInnerClass.staticMemberMethod();
	}
	
}

 

class A{
	/**정적 멤버 클래스**/
    static class C{
    	C(){} //생성자
        int field1; //인스턴스 필드
        static int field2; //정적 필드
        void method1(){} //인스턴스 메소드
        static void method2(){} //정적 메소드
	}
}

//객체

A.C c = new A.C();
c.field1 = 3;
c.method();
A.C.field2 = 3; //앞에 static이 있기 때문에 클래스명으로 호출 가능함.
A.C.method2();

 

2 - 생성자 또는 메소드 내부에서 선언되는 중첩 클래스를 로컬 클래스

생성자나 메소드 내부에 선언되어 있기 때문에 메서드가 실행할 때만 사용할 수 있다.

로컬 클래스는 접근 제한자(public,private) 및 static을 붙힐 수 없다. 어차피 메소드 내에서만 사용하므로 접근 제한할 필요가 없음. 인스턴스 필드와 메소드는 선언할 수 있지만 정적 필드와 메소드는 안됨.

 

class Outter3 {
	
	public void memberMethod() {
		int num = 10;
		
		// 로컬 내부클래스 선언부보다 윗쪽에서 접근 불가
//		LocalInnerClass lic = new LocalInnerClass(); // 컴파일에러
		
		// 3. 로컬 내부클래스
		// - 메서드(또는 생성자) 내부에 정의한 클래스
		// => 워크스페이스 bin 폴더에 외부클래스명$1내부클래스명.class 파일 생성됨
		//    (주의! $ 뒤에 숫자 1이 붙음)
		class LocalInnerClass {
			int localInnerMemberVariable = 10; // 멤버변수
//			static int localInnerstaticVariable = 20; // static 멤버 선언 불가
			
			public LocalInnerClass() {} // 생성자
			
			public void localInnerMemberMethod() { // 멤버메서드
				System.out.println("localInnerMemberMethod()");
			}
			
//			public static void localInnerStaticMemberMethod() { // 컴파일에러
//				System.out.println("localInnerStaticMemberMethod()");
//			}
		}
		
		// 로컬 내부클래스 정의 코드 아래쪽부터 
		// 현재 메서드가 종료될 때까지 로컬 내부클래스에 접근 가능
		// => 로컬 변수와 동일
		LocalInnerClass lic = new LocalInnerClass();
		System.out.println(lic.localInnerMemberVariable);
		lic.localInnerMemberMethod();
		
	}
	
	public void memberMethod2() {
		// 다른 메서드에서는 로컬 내부클래스 접근 불가 = 로컬 변수와 동일
//		LocalInnerClass lic = new LocalInnerClass(); // 컴파일에러
	}
	
}

 

멤버 클래스와 로컬 클래스는 변수로 바꿔 생각하면 이해가 쉬움

 

 

핸들러 클래스를 정의하는 방법 4단계

 

1. 추상 클래스 또는 인터페이스를 상속받는 핸들러 클래스를 별도로 정의

=> 주로 외부의 다른 클래스에서도 접근해야할 일이 많을 경우 사용

 

class MyHandler implements MyInterface {
	//추상클래스 또는 인터페이스에 정의된 추상메서드 오버라이딩 (myinterface 인터페이스 생성한 상태)
	@override 
    public void abstractMethod(){
    	System.out.println("MyHandler 클래스에서 오버라이딩 된 추상메서드");
    }

}


//메인 메서드에서

MyHandler handler = new MyHandler();
handler.abstractMethod();

 

2. 추상클래스 또는 인터페이스를 상속받는 핸들러 클래스를 내부클래스 형태로 정의

 

2-1)

=> 로컬 내부 클래스 형태로 정의

=> 주로 클래스 내의 특정 메서드에서만 접근하는 경우

public void method2(){
	class MyLocalInnerHandler implements MyInterface{
    	@Override
        public void abstractMethod(){
        	System.out.println("MyLocalInnerHandler 클래스에서 오버라이딩 된 추상메서드")
        };
    }

	MyLocalInnerHandler handler = new MyLocalInnerHandler();
    handler.abstractMethod();
}

 

 

2-2)

=> 인스턴스 내부 클래스 형태로 정의

=> 주로 클래스 내의 여러 메서드에서 접근하는 경우

 

class MyInnerHandler implements MyInterface {
	// 추상클래스 또는 인터페이스에 정의된 추상메서드 오버라이딩(구현)
	@Override
	public void abstractMethod() {
		System.out.println("MyInnerHandler 클래스에서 오버라이딩 된 추상메서드");
	}
		
}

//main메서드에서
Ex_Handler_2 ex = new Ex_Handler_2();
MyInnerHandler handler = ex.new MyInnerHandler();
handler.abstractMethod();
		
ex.method();

public void method() {
	// 일반 메서드 내에서는 일반적인 방법으로 접근 가능
	MyInnerHandler handler = new MyInnerHandler();
	handler.abstractMethod();
	}

 

 

3. 익명 내부 클래스(Anonymous Inner Class) 형태로 정의

 

- 핸들러 클래스를 별도로 정의하지 않고(=> 익명(Anonymous)) 구현체의 부모 타입을 바로 사용.

- 핸들러 클래스의 이름 없이 부모 타입의 이름을 사용하여 참조 변수 선언 및 인스턴스 생성을 한꺼번에 수행하는 것.

- 이 때, 인스턴스 생성 시점에서 추상메서드 구현까지 한꺼번에 수행하므로 인스턴스 생성 후 내부의 메서드 호출도 안전함.

- 이미 정의 시점에서 인스턴스 생성까지 수행하게 되므로 바로 변수를 통해 인스턴스 접근 가능.

=> 주로 여러 메서드에서 접근해야할 경우

 

< 기본 문법 >

추상클래스 또는 인터페이스 변수명 = new 추상클래스또는인터페이스명() {

        //추상메서드 오버라이딩 필수

}

 

인스턴스 생성 시 생성자 뒤에 중괄호를 명시하여 중괄호 내부에 추상메서드를 구현해야 한다.

 

public void method(){

MyInterface handler = new MyInterface(){
	
    @Override
    public void abstractMethod(){
    	System.out.println("익명 내부클래스에서 구현한 추상메서드!");
    }

}; 중괄호 뒤에 세미콜론 필수!
	//이미 인스턴스까지 생성되어 있으므로 로컬변수로 바로 접근 가능
	handler.abstractMethod();
}

 

 

 

4. 익명 내부 클래스(Anonymous Inner Class)의 임시 객체 형태로 정의

- 인스턴스 생성 과정에서 해당 주소를 저장할 참조 변수 없이 인스턴스만 생성하고 내부 추상메서드 구현을 수행

=> 주로 특정 메서드 파라미터로 인스턴스를 전달할 때 1회성 객체를 생성하여 인스턴스 주소만 전달하는 방법으로 사용

- 이 때, 인스턴스 생성 시점에서 추상메서드 구현까지 모두 처리

- 이미 정의 시점에서 인스턴스 생성까지 수행하게 되므로 파라미터를 전달받은 메서드 내에서는 인스턴스에 바로 접근 가능

 

< 기본 문법 >

new 추상클래스또는인터페이스명() {

    //추상메서드 오버라이딩 필수!

};

 

---------3단계 형태로 메서드 파라미터에 인스턴스 전달 시---------
//멤버 내부 클래스 형태의 익명 클래스 정의할 경우
public void method(){

MyInterface handler = new MyInterface(){
	
    @Override
    public void abstractMethod(){
    	System.out.println("익명 내부클래스에서 구현한 추창메서드");
    }
};


//여러 메서드에서 하나의 핸들러 객체로 파라미터를 전달받는 경우
method2(handler);
method3(handler);

//참조변수에 인스턴스 주소가 저장되므로 메서드 호출 후에도 인스턴스 접근 가능
handler.abstractMethod();

}


---------4단계 형태로 메서드 파라미터에 인스턴스 전달 시---------
//만악, 구현체 내부의 작업이 다르고 파라미터를 전달받는 메서드가 여러개일 경우
//=> 메서드 호출 시 파라미터에서 인스턴스 생성 및 구현 코드를 기술
//=> 참조변수 없이 인스턴스 생성 및 구현코드 자체를 파라미터로 전달

method2(new MyInterface() {

@Override
public void abstractMethod() {
System.out.println("익명 내부클래스의 임시객체1 에서 구현한 추상메서드!");
	}
			
});

//=> 메서드 호출 후에는 생성된 인스턴스에 접근 불가능
//(1회성 객체로 사용되고 난 후 가비지 컬렉터에 의해 제거됨)

// method3() 메서드에 전달하는 파라미터에는
// 또 다른 임시객체를 생성하여 다른 작업을 수행하도록 추상메서드를 구현

method3(new MyInterface() {

@Override
public void abstractMethod() {
System.out.println("익명 내부클래스의 임시객체2 에서 구현한 추상메서드!");
	}
			
});

}

	public void method2(MyInterface handler) {
		handler.abstractMethod();
	}
	
	public void method3(MyInterface handler) {
		handler.abstractMethod();
	}

'develop > Java' 카테고리의 다른 글

enum 타입  (0) 2021.04.12
예외(Exception)  (0) 2021.04.12
제네릭(Generic, 일반화)  (1) 2021.04.05
Collection Framework(컬렉션 프레임워크)  (0) 2021.04.04
java.lang / java.util  (0) 2021.03.23