숑숑이의 개발일기

본글은 'Do it 자바 완전 정복' 책을 통한 공부내용을 정리한 글입니다.

 

이너 클래스

클래스 내부에 포함되는 이너 클래스(inner class)는 인스턴스 멤버 이너 클래스, 정적 멤버 이너 클래스, 지역 이너 클래스로 나뉜다. 인스턴스 멤버와 정적 멤버 이너 클래스는 클래스의 멤버인 반면, 지역 이너 클래스는 메서드 내에서 정의하며 지역 변수처럼 해당 메서드 내부에서만 한정적으로 사용되는 클래스다.

 

인스턴스 멤버 이너 클래스

인스턴스 멤버 이너 클래스는 인스턴스 내부에 멤버의 형태로 존재한다. 이때 자신을 감싸고 있는 아우터 클래스의 모든 접근 지정자의 멤버에 접근할 수 있다. 이너 클래스는 독립적으로 사용할 수 없고, 반드시 아우터 클래스를 이용해야만 사용할 수 있다.

class 아우터 클래스 {
	class 이너 클래스 {
    }
}

해당 코드를 컴파일 하면 아우터 클래스.class 파일과 아우터 클래스$이너 클래스.class 파일이 생성된다.

 

인스턴스 이너 클래스 객체 생성하기

인스턴스 멤버 이너 클래스는 아우터 클래스의 객체 내부에 존재하므로 이너 클래스의 객체를 생성하기 위해서는 먼저 아우터 클래스의 객체 생성을 해야한다. 

아우터 클래스 아우터 클래스 참조 변수 = new 아우터 클래스();
아우터 클래스.이너 클래스 이너 클래스 참조 변수 = 아우터 클래스 참조 변수.new 이너 클래스();

class A {
	class B {}
}

A a = new A();
A.B = a.new B());
class A {
	public int a = 3;
    protected int b = 4;
    int c = 5;
    private int d = 6;
    void abc() {
    	System.out.println("A클래스의 abc()");
    }
    
    class B {
    	void bcd() {
        	System.out.println(a);
        	System.out.println(b);
        	System.out.println(c);
        	System.out.println(d);
            abc();
            // 아우터 클래스의 모든 멤버를 접근 지정자 상관없이 사용 가능
        }
    }
}

public class CreateObjectAndAccessMember {
	public static void main(String[] args) {
    	A a = new A();
        A.B b = a.new B();
        b.bcd();
    }
}

 

아우터 클래스의 객체 참조하기

아우터 클래스의 필드나 메서드와 동일한 이름을 이너 클래스 안에서 정의했을 때 이너 클래스 내부에서는 이너 클래스의 필드나 메서드가 참조된다. 이너 클래스 내부에서 this.의 의미는 이너 클래스 자신이 된다.

이너 클래스의 내부에서 아우터 클래스의 멤버를 참조하고 싶다면 아우터 클래스명.this를 명시적으로 붙여 사용한다.

class A {
	int a = 3;
    int b = 4;
    void abc() {
    	System.out.println("A클래스의 abc()");
    }
    
    class B {
    	int a = 5;
        int b = 6;
        void abc() {
        	System.out.println("B클래스의 abc()");
        }
    	void bcd() {
        	System.out.println(a);
        	System.out.println(b);
            abc();
            
            // 아우터 클래스의 멤버 호출 및 사용
            System.out.println(A.this.a);
            System.out.println(A.this.b);
            A.this.abc();
        }
    }
}

public class UseMembersOuterClass {
	public static void main(String[] args) {
    	A a = new A();
        A.B b = a.new B();
        b.bcd();
    }
}

정적 멤버 이너 클래스

정적 멤버 이너 클래스는 이너 클래스 앞에 static 키워드가 포함된 이너 클래스다. 아우터 클래스의 정적 멤버에만 접근할 수 있는데 이는 정적 특정이고 아우터 클래스의 객체를 생성하지 않아도 정적 이너 클래스의 객체를 생성해 사용할 수 있어야 한다는 말이다. 즉, 아우터 클래스의 멤버 중 객체 생성 없이 바로 사용할 수 있는 정적 멤버만 정적 이너 클래스 내부에서 사용할 수 있다는 것이다.

 

정적 이너 클래스 객체 생성하기

정적 이너 클래스도 정적 멤버이므로 클래스명으로 바로 접근할 수 있다.

아우터 클래스.이너 클래스 이너 클래스 참조 변수 = new 아우터 클래스.이너 클래스();

class A {
	static class B {}
}

A.B b = new A.B();

주의해야 할 점은 아우터 클래스의 객체 생성 전에도 사용할 수 있어야 하므로 정적 이너 클래스 내부에서는 아우터 클래스의 정적 멤버만 사용할 수 있다는 것이다.

 

지역 이너 클래스

지역 이너 클래스는 메서드 내에서 정의되는 클래스다. 일반적으로 지역 이너 클래스는 선언 이후 바로 객체를 생성해 사용하며, 메서드가 호출될 떄만 메모리에 로딩된다. 컴파일 시 생성도는 클래스명이 조금 독특하다. 아우터 클래스$+숫자+지역 이너 클래스.class

 

지역 이너 클래스 객체 생성하기

지역 이너 클래스도 아우터 클래스의 멤버를 접근 지정자와 상관없이 사용할 수 있다. 추가로 자신이 정의된 메서드의 지역 변수도 클래스 내부에서 사용할 수 있다. 단, 지역 변수를 사용할 때에는 반드시 해당 지역 변수가 final로 선언되어야 한다.

 

지역 이너 클래스 지역 이너 클래스 참조 변수 = new 지역 이너 클래스();
class A {
	int a = 3;
    void abc() {
    	int b = 5;	// 지역 변수
        class B {	// 지역 이너 클래스
        	void bcd() {
            	System.out.println(a);
            	System.out.println(b);
                a = 5;
            }
        }
        
     	B bb = new B();
        bb.bcd();
    }
}

public class AccessMemberLocalVariable {
	public static void main(String[] args) {
    	A a = new A();
        a.abc();
    }
}

 

익명 이너 클래스

이제는 익명 이너 클래스를 자세하게 알아본다. 

 

익명 이너 클래의 정의와 특징

익명 이너 클래스는 정의된 위치에 따라 분류할 수 있는데, 클래스의 중괄호 바로 아래에서 사용시 인스턴스 익명 이너 클래스, 메서드 내부에서 사용했을 때는 지역 익명 이너 클래스를 의미한다.

// 인터페이스를 상속한 이너 클래스를 생성해 인터페이스 객체 생성
class A {
	C c = new B();
    void abc() {
    	c.bcd();
    }
    class B implements C {
    	public void bcd() {
        	System.out.println("인스턴스 이너 클래스");
        }
    }
}

interface C {
	public abstract void bcd();
}

public class AnnoymousClass {
	public static void main(String[] args) {
    	A a = new A();
        a.abc();
    }
}
// 익명 이너 클래스를 활용해 인터페이스 객체 생성
class A {
	C c = new C() {
    	public void bcd() {
        	System.out.println("익명 이너 클래스");
        }
    }
    void abc() {
    	c.bcd();
    }
}

interface C {
	public abstract void bcd();
}

public class AnnoymousClass2 {
	public static void main(String[] args) {
    	A a = new A();
        a.abc();
    }
}

이번에는 인터페이스 C를 구현한 자식 클래스에서 메서드를 추가한 경우를 살펴보자

interface C {
	public abstract void bcd();
}

class B implements C {
	public void bcd() {
    	// 오버라이딩 메서드
    }
    public void cde() {
    	// 추가 메서드
    }
}

public class test {
	public static void main(String[] args) {
    	B b = new B();
        b.bcd();	// o
        b.cde();	// o
        
  		C c = new C() {
        	public void bcd() {
            	cde(); // 내부적으로 호출 가능
            }
            public void cde () {
            	// 추가 메서드
            }
        };
        
        c.bcd();	// o
        c.cde();	// x
    }
}

상단의 자식 클래스 타입으로 객체를 선언하고 생성시에는 오버라이딩된 메서드와 추가 메서드 모두 사용할 수 있다. 반면 익명 이너 클래스를 사용할 때에는 항상 부모 타입으로만 선언할 수 있으므로 추가로 정의한 메서드는 항상 호출할 수 없다. 그러나 오버라이딩 메서드 내부에서는 호출할 수 있으므로 작성해야 할 내용이 많을 때 분리해 작성하는 것이 효율적이다. 

 

 

익명 이너 클래스를 활용한 인터페이스 타입의 입력매개변수 전달

이번에는 인터페이스 타입의 입력매개변수로 익명 이너 클래스를 이용해 생성한 객체를 전달하는 방법을 알아본다. 다음 코드에서는 인터페이스 A 타입의 객체를 입력매개변수로 포함하고 있는 메서드 cde(A a)가 있다.

interface A {
	public abstract void abc();
}

class C {
	void cde(A a) {
    	a.abc();
    }
}

이때 클래스 C의 객체를 생성한후 cde() 메서드를 호출하기 위해서는 입력매개변수로 사용될 인터페이스 A 타입의 객체를 생성해야 한다. 인터페이스로는 객체를 직접 생성할 수 없으므로 인터페이스 A를 구현한 자식 클래스의 객체가 전달되어야 한다. 방법은 크게 4가지 형태로 분류할 수 있다. 먼저 다음과 같이 자식 클래스를 직접 정의한 2가지 형태를 살펴본다.

interface A {
	public abstract void abc();
}

class B implements A {
	public void abc() {
    	System.out.println("입력매개변수 전달");
    }
}

class C {
	void cde(A a) {
    	a.abc();
    }
}

public class AnonymousClass3 {
	public static void main(String[] args) {
    	// 방법 1. 클래스명 o + 참조 변수명 o
        C c = new C();
        A a = new B();
        c.cde(a);
        
        // 방법 2. 클래스명 o + 참조 변수명 x
        c.cde(new B());
    }
}

이번에는 자식 클래스를 정의하지 않고, 익명 클래스를 사용해 메서드 입력매개변수로 객체를 전달하는 방법을 살펴본다

interface A {
	public abstract void abc();
}
Class C {
	void cde(A a) {
    	a.abc();
    }
}
public class AnonymousClass4 {
	public static void main(String[] args) {
    	C c = new C();
        // 방법 3. 클래스명 x + 참조 변수명 o
        A a = new A() {
        	public void abc() {
            	System.out.println("입력매개변수 전달");
            }
        };
        c.cde(a);
        
        // 방법 4. 클래스명 x + 참조 변수명 x
        c.cde(new A() {
        	public void abc() {
            	System.out.println("입력매개변수 전달");
            }
        });
        
    }
}

추상 클래스의 객체 생성 때에 언급한 바와 같이 익명 이너 클래스를 사용하면 클래스를 추가로 정의해야 하는 수고로움을 덜 수도 있다. 다만 여러 개의 객체를 생성하고자 할 때는 객체를 생성할때 마다 추상 메서드를 구현해야 하므로 직접 클래스를 추가로 작성하는 것이 좋다.

 

이너 인터페이스

인터페이스를 클래스 내부에 정의하는 것은 해당 클래스에 의존적인 기능을 수행할 때다. 예를 들어 버튼 클릭을 감지하는 인터페이스는 버튼 클래스 내부에 위치시키키는 것이 바람직 할 것이다. 이너 인터페이스는 사용자 인터페이스의 이벤트 처리에 가장 많이 사용된다. 사용자 인터페이스에서 이벤트를 감지하는 인터페이스를 '리스너'라고 한다.

 

이너 인터페이스의 정의와 특징

정적 이너 인터페이스만 존재할 수 있다. 이너 인터페이스 앞의 static 제어자를 생략시에는 컴파일러가 자동으로 추가해 준다.

class A {
	static interface B {
    	void bcd();
    }
}

컴파일 하면 이너 클래스와 같이 아우터 클래스명$이너 인터페이스명.class 형태로 .class 파일이 생성된다. 객체를 생성하기 위해서는 해당 인터페이스를 상속한 자식 클래스를 생성한 후 생성자를 이용하거나, 익명 이너 클래스를 활용해 객체를 생성해야 한다. 유일한 차이점은 클래스 내부의 인터페이스가 있으므로 객체의 타입을 아우터 클래스명.이너 인터페이스명과 같이 사용해야 한다.

class A {
	interface B {
    	public abstract void bcd();
    }
}

class C implements A.B {
	public void bcd() {
    	System.out.println("이너 인터페이스 구현 클래스 생성");
    }
}

public class CreateInnterInterface {
	public static void main(String[] args) {
    	// 객체 생성 방법 1(자식 클래스 직접 생성)
        A.B ab = new C();
        C c = new C();
        c.bcd();
        
        // 객체 생성 방법2(익명 이너 클래스 생성)
        A.B b = new A.B() {
        	public void bcd() {
            	System.out.println("익명 이너 클래스로 객체 생성");
            }
        };
        b.bcd();
        
    }
}

 

이벤트 처리 기능 작성하기

class Button {	// 기본적인 사용자 인터페이스 클래스는 API로 제공됨
	OnClickListener (OnClickListener ocl) {
    	this.ocl = ocl;
    }
    interface OnClickListener {
    	public abstract void onClick();
    }
    void onClick () {
    	ocl.onClick();
    }
}

public class ButtonAPI {
	public static void main(String[] args) {
    	Button btn1 = new Button();
        btn1.setOnClickListener(new Button.OnClickListener() {
        	@Override
            public void onClick() {
            	System.out.println("음악 재생");
            }
        });
        btn1.onClick();
        
        Button btn2 = new Button();
        btn2.setOnClickListener(new Button.OnClickListener() {
        	@Override
            public void onClick() {
            	System.out.println("링크 연결");
            }
        });
    }
}
profile

숑숑이의 개발일기

@숑숑-

풀스택 개발자 준비중입니다