[이것이 자바다 - 9] 중첩 클래스와 중첩 인터페이스

Uicheon·2022년 6월 9일
1

이것이 자바다

목록 보기
7/8
post-thumbnail

9. 중첩 클래스와 중첩 인터페이스


왠일로 정상적인 그림


9.1 중첩 클래스와 중첩 인터페이스란?

OOP의 클래스들은 서로 관계를 맺고 상호작용을 한다.
클래스가 여러 클래스와 관계를 맺는 경우, 독립적으로 선언하는 것이 좋다.
그러나, 특정 클래스와 관계를 맺을 경우 관계 클래스를 클래스 내부에 선언하는 것이 좋다.

중첩 클래스(Nested Class)란 클래스 내부에 선언한 클래스이다.
중첩 클래스를 사용하면 두 클래스 멤버들을 서로 쉽게 접근할 수 있다.
또한, 외부에 불필요한 관계 클래스를 감춤으로써 코드의 복잡성을 줄인다.

class ClassName{
	class NestedCalssName{
    }
}

인터페이스도 클래스 내부에 선언할 수 있다.
이런 인터페이스를 중첩 인터페이스라고 한다.
인터페이스를 클래스 내부에 선언하는 이유는 해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 위해서이다.

class ClassNamme{
	interface NestedInterfaceName{
    }
}

중첩 인터페이스는 주로 UI 프로그래밍의 이벤트 처리에 사용된다.

public class View{
	public interface OnClickListener{
    	public void onClick(View v);
	}
}

9.2 중첩 클래스

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

  • 멤버 클래스: 클래스 멤버로서 선언되는 중첩 클래스
  • 로컬 클래스: 메소드 내부에서 선언되는 중첩 클래스

멤버 클래스도 클래스이기에, 컴파일 하면 바이트 코드 파일(.class)가 생성된다.

바이트 코드 파일 이름

멤버클래스
A바깥클래스 $ B멤버클래스.class\substack{\underline{\large{A}}\\바깥클래스} \space \$ \space \substack{\underline{\large{B}}\\{멤버 클래스}} .class

로컬클래스
A바깥클래스 $1 B멤버클래스.class\substack{\underline{\large{A}}\\바깥클래스} \space \$1 \space \substack{\underline{\large{B}}\\{멤버 클래스}} .class

9.2.1 인스턴스 멤버 클래스

  • static 키워드 없이 선언된 클래스다.
  • 인스턴스 필드/메소드만 선언이 가능하다.
  • 정적 필드/메소드는 선언할 수 없다.
class A{
	class B{
    	B() {}
        int field1;
        //static int field2; 		정적 필드 X
        void method1(){}
        //static void method2()		정적 메소드 X
    }
}

//A클래스 외부에서 인스턴스 멤버 클래스 B를 호출하려면,
//A 객체를 먼저 생성하고 B 객체를 생성한다.
A a= new A();
A.B b = a.new B();
b.field1 = 3;
b.method1();

9.2.2 정적 멤버 클래스

  • static 키워드로 선언된 클래스를 말한다
  • 모든 종류의 필드/메소드 선언 가능
class A{
	static class C{
    	C() {}
        int field1;
        static int field2; 		
        void method1(){}
        static void method2()		
    }
}

//A클래스 외부에서 정적 멤버 클래스 C를 바로 호출한다.
A.C c = new A.C();
c.field1 = 3;
c.method1();
A.C.field2 = 3;
A.C.method2();

9.2.3 로컬 클래스

중첩 클래스는 메소드 내에서도 선언할 수 있다.
이것을 로컬 클래스라고 한다.
접근 제한자(public, private), static 이용이 불가능하다.
(메소드 내부에서만 사용되기에)

로컬 클래스 내부에는 인스턴스 필드와 메소드만 선언 가능하다
정적 필드, 정적 메소드 선언할 수 없다.

메소드 안에 들어가있다는 차이점을 제외하고
인스턴스 멤버 클래스와 같다

비동기 처리를 위해 스레드 객체 생성에 사용된다.

9.3 중첩 클래스의 접근 제한

9.3.1 바깥 필드와 메소드에서 사용 제한

복잡하다
멤버 클래스가 인스턴스, 정적으로 선언됨에 따라 바깥 클래스 필드, 메소드 사용 제한이 생긴다.

  • 인스턴스 멤버 클래스는 바깥 클래스의 인스턴스 필드의 초기값, 인스턴스 메소드에서 객체를 생성할 수 있다.
  • 그러나 정적 필드의 초기값, 정적 메소드에서는 객체를 생성할 수 없다.
  • 반면, 정적 멤버 클래스는 모든 필드의 초기값, 모든 메소드에서 객체 생성할 수 있다.
public class A{
	//인스턴스 필드
    B field1 = new B();
    C fiedl2 = new C();
    
    //인스턴스 메소드
    void method1(){
    	B var1 = new B();
        C var2 = new C();
    }
    
    //정적 필드 초기화
    //static B field3 = new B();		불가능
    static C filed4 = new C():
    
    //정적 메소드
    static void method2(){
    	//B var1 = new B() 				불가능
        C var2 = new C():
    }
    
    //인스턴스 멤버 클래스
    class B{}
    static class C{}
}

9.3.2 멤버 클래스에서 사용 제한

멤버 클래스가 인스턴스 또는 정적으로 선엄됨에 따라, 멤버 클래스 내부에서 바깥 클래스의 필드와 메소드를 접근할 때에도 제한이 따른다.

  • 인스턴스 멤버 클래스(B) 안에서는 바깥 클래스의 모든 필드와 메소드에 접근할 수 있다.
  • 정적 멤버 클래스(C) 안에서는 바깥 클래스의 정적 필드와 메소드에만 접근할 수 있고, 인스턴스 필드와 메소드는 접근 할 수 없다.

슬슬 화가나려한다. 뭔가 꼬아놓은 느낌인데 익숙해질려면 꽤 걸릴듯

한마디로 바깥 클래스에서 내부 클래스 접근과 반대이다.
왜 그럴까?가 궁금하다면

9.3.3 로컬 클래스에서 사용 제한

로컬 클래스 내부에서는 바깥 클래스의 필드/메소드를 제한 없이 사용할 수 있다.
문제는 메소드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때이다.
로컬 클래스의 객체는 메소드 실행이 끝나도 힙 메모리에 존재해 계속 사용할 수 있다.
매개 변수나 로컬 변수는 메소드 실행이 끝나면 스택 메모리에서 사라지기 때문에 로컬 객체에서 사용할 경우 문제가 발생한다.

자바는 이 문제를 해결하기 위해 컴파일 시 로컬 클래스에서 사용하는 매개 변수, 로컬 변수의 값을 로컬 클래스 내부에 복사해 두고 사용한다.
그리고 매개 변수, 로컬 변수가 수정된다면, 로컬 클래스에 복사해둔 값과 달라지는 문제를 해결하기위해 매개 변수, 로컬 변수를 final로 선언해서 수정을 막는다.

결론적으로 로컬 클래스에서 사용 가능한 것은 final로 선언된 매개 변수, 로컬 변수뿐이다.

JAVA8 이후
final 키워드가 있다면 로컬클래스의 메소드 내부에 지역 변수로 복사된다.
final 키워드가 없다면 로컬 클래스의 필드로 복사된다

우리는 복사 위치에 신경 쓸필요 없다.
로컬 클래스에서 사용된 매개 변수, 로컬 변수가 final 속성을 갖는다는 것만 알면 된다.

9.3.4 중첩 클래스에서 바깥 클래스 참조 얻기

클래스 내부에서 this는 객체 자신의 참조이다.
중첩 클래스에서 this는 중첩 클래스의 객체 참조이다.
중첩 클래스 내부에서 바깥 클래스의 객체 참조를 얻으려면 바깥 클래스의 이름을 this앞에 붙여야한다.

바깥클래스.this.필드
바깥클래스.this.메소드();

public class Outter{
	String field = "Outter-field";
    void method(){
    	sout("Outter-method");
    }
    
    class Nested{
    	String field = "Nested-field";
        void method(){
        	sout("Nested-method");
        }
        
        void print(){
        	sout(this.field);
            this.method():
            sout(Outter.this.field);
            Outter.this.method();
        }
    }
}

psvm(String args){
	Outter outter = new Outter();
    Outter.Nested nested = outter.new Nested();
    nested.print();
    
    
    //Nested-field
    //Nested-method
    //Outter-field
    //Outter-field

}

9.4 중첩 인터페이스

중첩 인터페이스는 클래스의 멤버로 선언된 인터페이스를 말한다.

인터페이스를 클래스 내부 선언하는 이유?
해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위해서이다.
특히 UI 프로그래밍에서 이벤트를 처리할 목적으로 많이 활용된다.

Button 클리 시 이벤트 처리를 한다 하자.
그러나 아무 객체가 아니라, Button 내부의 중첩 인터페이스를 구현한 객체만 받는다고 해보자.

public class Button{
	OnClickListener listener;			//인터페이스 타입 필드
    
    void setOnClickListener(OnClickListener listener){ //다형성
    	this.listener = listener;
    }
    
    void touch(){				//구현 객체의 onClick() 호출
    	listener.onClick();
    }
    
    interface OnClickListener{ //중첩 인터페이스
    	void onClick();
    }
}

중첩 인터페이스 타입으로 필드를 선언하고 Setter로 구현 객체를 받아 필드에 주입한다.

버튼 이벤트가 발생했을 떄(touch()가 실행 될 때) 인터페이스를 통해 구현 객체의 메소드를 호출한다.

public class CallListener implements Button.OnClickListener{
	@Override
    public void onClick(){
    	sout("전화를 건다");
    }
}

public class MessageListener implements Button.OnClickListener{
	@Override
    public void onClick(){
    	sout("메시지를 보낸다");
    }
}

psvm(String args[]){
	Button btn = new Button();
    
    btn.setOnClickListener(new CallListener());
    btn.touch();
    
    btn.setOnClickListener(new MessageListener());
    btn.touch();
    
    //전화 건다
    //메시지를 보낸다.
}

9.5 익명 객체

익명 객체는 이름이 없는 객체다.

  • 단독으로 생성할 수 없다.
  • 클래스를 상속하거나, 인터페이스를 구현해야만 생성가능하다.
  • 필드의 초기값, 로컬 변수의 초기값, 매개변수의 매개값으로 주로 대입된다.
  • UI 이벤트 처리, 스레드 객체를 간편하게 생성할 목적으로 활용된다.

9.5.1 익명 자식 객체

부모 타입으로 필드/변수를 선언하고, 자식 객체를 초기값으로 대입할 경우를 생각해보자.
만약 자식 클래스가 재사용되지 않고, 오로지 해당 필드와 변수의 초기값으로만 사용된다면?
익명 자식 객체를 생성하여 초기값으로 대입하는 것이 좋은 방법이다.

부모클래스 [필드|변수] = new 부모클래스(매개값, ...){
	//필드
    //메소드
};

부모 클래스(매개값, ...)은 부모 클래스를 상속해서 중괄호 {}로 자식 클래스를 선언하라는 뜻이다.
부모 클래스(매개값, ...)은 부모 생성자를 호출한다.

중괄호{}에서는 자식 객체의 필드/메소드, 부모 메소드를 오버라이딩 할 수 있다.

단, 일반 클래스처럼 생성자를 선언할 수 없다.

메소드의 매개 변수가 부모 타입일 경우 메소드 호출 코드에서 익명 자식 객체를 생성해서 매개값으로 대입할 수도 있다.

// 기존 방식
class Child extends Parent{} 			//자식 클래스

class A{
	Parent field = new Child();			//필드에 자식 대입
    void method(){
    	Parent localVar = new Child()	//로컬 변수에 자식 객체 대입
    }
}

// 필드 선언시
Class A{
	Parent field = new Parent(){			//A 클래스의 필드 선언
    	int childField;
        void childMethod(){}
        
        @Override							//Parent method 오버라이딩
        void parentMethod(){}
    };										//실행문이므로 세미콜론 필수
}

//메소드내 로컬 변수 선언
Class A{
	void method(){
      Parent field = new Parent(){			//A 클래스의 메소드의 로컬 변수 선언
          int childField;
          void childMethod(){}

          @Override							//Parent method 오버라이딩
          void parentMethod(){}
      };									
    }
}

//메소드 호출 코드에서 매개값으로 대입
Class A{
	void method1(Parent parent){}
    void method2(){
		method1(
          new Parent(){			
          int childField;
          void childMethod(){}
          @Override							
          void parentMethod(){}
          });
	}
}

익명 자식 객체에 새롭게 정의된 필드/메소드는 익명 자식 객체 내부에서만 사용되고, 외부에서 접근할 수 없다.
(익명 자식 객체는 부모 타입 변수에 대입되므로 부모 타입만 사용가능하다.)

9.5.2 익명 구현 객체 생성

인터페이스 타입으로 필드/변수를 선언하고, 구현 객체를 초기값으로 대입하는 경우를 생각해보자.

class TV implements RemoteControl{}

class A{
	RemoteControl field = new TV();				//필드에 구현 객체 대입
    void method(){
    	RemoteControl localVar = new TV() 		//로컬 변수에 구현 객체를 대입
    }
}

그러나, 구현 클래스가 일회성으로 사용된다 하자.
오로지 해당 필드와 변수의 초기값으로만 사용된다면, 익명 구현 객체를 초기값으로 대입하자.

인터페이스 [필드|변수] = new 인터페이스(){
	//인터페이스에 선언된 추상 메소드의 실체메소드
    //필드
    //메소드
};

인터페이스() {}는 구현 객체를 생성하라는 뜻이다.
중괄호 {}에는 구현 객체로, 모든 추상메소드를 오버라이딩해야한다. 또한 추가적인 필드/메소드 추가가 가능하다.
단, 실체 메소드에서만 사용이 가능하고, 외부에서는 사용하지 못한다.

public class Button{
	OnClickListener listener;			//인터페이스 타입 필드
    
    void setOnClickListener(OnClickListener listener){ //다형성
    	this.listener = listener;
    }
    
    void touch(){				//구현 객체의 onClick() 호출
    	listener.onClick();
    }
    
    interface OnClickListener{ //중첩 인터페이스
    	void onClick();
    }
}

public class Window{
	Button button1 = new Botton();
    Button button2 = new Botton();
	
    //필드 초기값 대입
    Button.OnClickListener listener = new Button.OnClickListener(){
    	@Override
        public void onClick(){
        	sout("전화 걸기");
        }
	};
    
    Window(){
      button1.setOnClickListener(listener);
      button2.setOnClickListener(new Button.OnClickListener(){
          @Override
          public void onClick(){
              sout"메시지 보냄");
          }
      });
	}
}

psvm(String[] args){
	Window w = new Windw();
    w.button1.touch();
	w.button2.touch();
    
    //전화 걸기
    //메세지 보냄
}

9.5.3 익명 객체의 로컬 변수 사용

익명 객체 내부에서는 바까 클래스의 필드/메소드는 제한 없이 사용할 수 있다.
문제는 메소드의 매개변수나, 로컬 변수를 익명 객체에서 사용할 때다.
메소드 내에서 생성된 익명 객체는 메소드 실행이 끝나도 힙 메모리에 존재해서 계속 사용할 수 있다.
매개/로컬 변수는 메소드 실행이 끝나면 stack에서 사라지기에 문제가 될 수 있다.

로컬 클래스에서 사용 제한에 대해 설명한 것과 똑같은 방법으로 해결된다.
익명 객체 내부에서 매개 변수/로컬 변수를 사용할 경우 final 특성을 가져야한다.

익명 객체에서 사용된 매개 변수와 로컬 변수는 모두 final 특성을 갖는다.


아.. 이번 강은 개인적으로 아쉽다.
아는 내용도 아니었거니와 점점 복잡해지는 제약 사항에 머리가 아팠다.
그래도 한번 정리할 수 있어서 다행이다.

profile
컨셉입니다~

0개의 댓글