본문 바로가기
언어/자바의 정석

[07-8] 내부 클래스(inner class), 익명 클래스(anonymous class)

by chan10 2021. 2. 17.

내부 클래스(inner class)

o  내부 클래스는 클래스가 클래스 내에 선언된다는 점을 제외하면 일반 클래스와 다르지 않다. 내부 클래스의 몇 가지 특징만 잘 이해하면 실제로 활용하는데 어려움이 없을 것이다.

o  내부 클래스는 사용빈도가 높지 않으므로 내부 클래스의 기본 원리와 특징을 이해하는 정도 까지만 알아본다.

 

내부 클래스란?

o  내부 클래스는 클래스 내에 선언된 클래스로 클래스에 다른 클래스를 선언하는 이유는 다음과 같은 장점이 있기 때문이다.

1.     내부 클래스에 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
두 클래스의 멤버들 간에 서로 쉽게 접근할 수 있다.

 

2.     코드의 복잡성을 줄일 수 있다.(캡슐화)

외부에 불필요한 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다.

 

내부 클래스의 종류와 특징

o  내부 클래스는 변수를 선언하는 것과 같은 위치에 선언할 수 있으며 변수처럼 선언하는 위치에 따라 다음과 같이 구분된다.

내부 클래스

특징

인스턴스 클래스

(instance class)

외부 클래스의 멤버 변수 선언위치에 선언하며, 외부 클래스의 인스턴스 멤버처럼 다루어진다. 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언된다.

스태틱 클래스

(static class)

외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static멤버처럼 다루어진다. 주로 외부 클래스의 static멤버, 특히 static메서드에서 사용될 목적으로 선언된다.

지역 클래스

(local class)

외부 클래스의 메서드나 초기화 블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다.

익명 클래스

(anonymous class)

클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)

  

 

내부 클래스의 선언

o  아래 오른쪽 코드에는 외부 클래스 3개에 서로 다른 종류의 내부 클래스를 선언했다. 선언된 위치에 따라 같은 선언위치의 변수와 동일한 유효범위와 접근성을 갖는다.

class Outer {
    int iv=0;    //인스턴스 변수
    static int cv=0;//static변수
    void myMethod() {
        int lv=0;//지역변수
    }
}
class Outer {
    class InstanceInner { }    // 인스턴스 클래스
    static class StaticInner { } // static 클래스
    void myMethod() {
        class LocalInner{ }    //지역 클래스
    }
}

 

 

내부 클래스의 제어자와 접근성

o  인스턴스 클래스와 스태틱 클래스는 외부 클래스의 멤버 변수(인스턴스, 클래스 변수)와 같은 위치에 선언하며 멤버 변수와 같은 성질을 갖는다. 내부 클래스가 외부 클래스의 멤버로 간주되며 인스턴스 변수와 클래스 변수 간의 규칙 또한 적용된다.

o  내부 클래스도 클래스이기 때문에 abstract, final과 같은 제어자를 사용할 수 있으며 멤버 변수처럼 privateprotected의 접근 제어자도 사용이 가능하다.

 

package JavaProject;
 
import JavaProject.InnerEx1.InstanceInner;
import JavaProject.InnerEx1.StaticInner;
 
public class Test {    
    public static void main(String[] args) {
        System.out.println(InstanceInner.CONST);
        System.out.println(StaticInner.cv);
    }
}
 
class InnerEx1 {
    class InstanceInner {
        int iv=100;
//        static int cv=100;    //에러, static변수를 선언할 수 없다.
        final static int CONST=100;//final static은 상수이므로 허용
    }
    
    static class StaticInner{
        int iv=200;
        static int cv=200;//static클래스만 static멤버를 정의할 수 있다.
    }
    
    void myMethod() {
        class LocalInner {
            int iv=300;
//            static int cv=300;
            final static int CONST=300;
        }
    }
}

-       내부 클래스 중 스태틱 클래스만 static멤버를 가질 수 있다. 내부 클래스에 static변수를 선언해야 한다면 스태틱 클래스로 선언해야 한다.

-       다만 finalstatic이 동시에 붙는 변수는 상수이므로 모든 내부 클래스에서 정의가 가능하다.

 

class InnerEx2 {
    class InstanceInner {}    //인스턴스 멤버
    static class StaticInner {}    //스태틱 멤버
    
    InstanceInner iv = new InstanceInner();
    static StaticInner cv = new StaticInner();
    
    static void staticMethod() {
        // static 멤버는 인스턴스 멤버에 접근 불가
//        InstanceInner obj1 = new InstanceInner();
        StaticInner obj2 = new StaticInner();
        
        // 굳이 접근 시 외부 클래스 먼저 생성
        InnerEx2 outer = new InnerEx2();
        InstanceInner obj1 = outer.new InstanceInner();
    }
    
    void instanceMethod() {
        //인스턴스 메서드에서는 인스턴스,static멤버 접근 가능
        InstanceInner obj1 = new InstanceInner();
        StaticInner obj2 = new StaticInner();
//        지역[myMethod()]에 선언된 내부 클래스는 외부 접근 불가
//        LocalInner lv = new LocalInner();
    }
    
    void myMethod() {
        class LocalInner { }
        LocalInner lv = new LocalInner();
    }
}

-       인스턴스 클래스는 외부 클래스의 인스턴스 멤버를 객체(외부 클래스) 생성 없이 바로 사용할 수 있지만, static 클래스는 외부 클래스의 인스턴스 멤버를 객체(외부 클래스) 생성 없이 사용할 수 없다.

-       마찬가지로 인스턴스 클래스는 static클래스의 멤버들을 객체 생성 없이 사용할 수 있지만, static 클래스에서는 인스턴스 클래스의 멤버들을 객체 생성 없이 사용할 수 없다.

 

class InnerEx3 {
    private int outerIv=0;
    static int outerCv=0;
    
    class InstanceInner {
        int iiv=outerIv;//외부 클래스의 private멤버도 접근 가능
        int iiv2=outerCv;
    }
    
    static class StaticInner {
        //static 클래스는 외부클래스의
        //인스턴스 멤버에 접근할 수 없다.
//        int siv=outerIv;
        int scv=outerCv;
    }
    
    void myMethod() {
        int lv=0//final 생략 상태
        final int LV=0//final 생략 가능
//        lv=1;     //final 생략상태 이기에 값 변경 시 컴파일 에러 발생
        
        class LocalInner {
            int liv=outerIv;
            int liv2=outerCv;
            int liv3=lv;    //JDK1.8이전 버전은 에러
            int liv4=LV;
        }
    }    
}

-       static 클래스(StaticInner)는 외부 클래스의 static멤버이기에 static멤버만 사용할 수 있다.

-       지역 클래스(LocalInner)는 외부 클래스의 인스턴스 멤버, static멤버를 사용할 수 있으며 지역 클래스가 포함된 메서드의 지역 변수도 사용할 수 있다.

-       , JDK1.8이전 버전에서는 final이 붙은 지역 변수만 접근이 가능한데 이유는 메서드가 수행을 마치고 지역변수가 소멸된 시점에도 지역 클래스의 인스턴스가 소멸된 지역 변수를 참조하려는 경우가 발생할 수 있기 때문이다.

-       JDK1.8부터 지역 클래스에서 접근하는 지역 변수 앞에 final을 생략할 수 있게 바뀌었으며 컴파일러가 자동으로 붙여준다. 그렇기에 해당 변수의 값이 바뀌는 문장이 있으면 컴파일 에러가 발생한다.

 

※상수들(final이 붙은 변수) constant pool이라는 곳에 저장해서 따로 관리되기에 다른 지역변수와 달리 메서드가 종료되어도 값이 유지된다. , 상수는 별도의 저장공간에 관리된다. 또한 constant pool에 있는 데이터들은 애플리케이션이 종료 시 메모리에서 사라진다. (상수도 마찬가지)

 

public class Test {    
    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.method1();
    }
}
 
class Outer {
    int value=10//Outer.this.value
    
    class Inner {
        int value=20//this.value
        
        void method1() {
            int value=30// value
            
            System.out.println("           value : " + value);
            System.out.println("      this.value : " + this.value);
            System.out.println("Outer.this.value : " + Outer.this.value);
        }
    }//Inner클래스 끝
}//Outer클래스 끝

-       외부 클래스의 선언된 변수의 이름이 같을 때 변수 앞에 ‘this’또는 ‘외부클래스명.this’를 붙여서 구별할 수 있다.

 

익명 클래스(anonymous class)

o  익명 클래스는 다른 클래스들과 달리 이름이 없다. 클래스의 선언과 객체 생성을 동시에 하기 때문에 한 번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.

o  이름이 없기에 생성자도 가질 수 없으며 조상클래스 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의한다. 따라서 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다. 오직 단 하나의 클래스를 상속받거나 단 하나의 인터페이스만을 구현할 수 있다.

new 조상클래스이름() {
       // 멤버 선언
}
 
     또는
 
new 구현인터페이스이름() {
       // 멤버 선언
}
class InnerEx6 {
    Object iv = new Object() {void method(){}};        //익명 클래스
    static Object cv = new Object() {void method(){}};    // 익명 클래스
    
    void myMethod() {
        Object lv = new Object() {void mehtod(){}};    // 익명 클래스
    }    
}

위 예제를 컴파일하면 다음과 같이 4개의 클래스가 생성된다.

InnerEx6.class

InnerEx6$1.class  // 익명 클래스

InnerEx6$2.class  // 익명 클래스

InnerEx6$3.class  // 익명 클래스

 

익명 클래스는 이름이 없기에 외부 클래스명$숫자.class’의 형식으로 클래스파일명이 결정된다.

 

o  첫 번째 코드를 익명 클래스로 적용한 것이 두 번째 코드이다.

class InnerEx7{
    public static void main(String[] args) {
        Button b = new Button("Strat");
        b.addActionListener(new EventHandler());
    }
}
 
class EventHandler implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("ActionEvent occurred!!!");
    }
}
class InnerEx8{
    public static void main(String[] args) {
        Button b = new Button("Strat");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("ActionEvent occurred!!!");
                }
            }//익명 클래스 끝
        );
    }// main의 끝
}// InnerEx8의 끝

 

public class Test {    
    public static void main(String[] args) {
        // 일반 객체 생성
        Sum s = new Sum();
        s.print();
        
        // 익명 클래스로 Sum클래스 상속
        Sum s2 = new Sum() {
            public void print() {
                x=5;
                System.out.println("x="+x+", y="+y);
                System.out.println("sum2 = "+(x+y));
            }
        };// 익명 클래스 끝
        s2.print();
        
        //익명 클래스로 인터페이스 구현
        InterSum is = new InterSum() {
            @Override
            public void print() {
                System.out.println("anonymous class!!");    
            }
        };// 익명 클래스 끝
        is.print();
    }
}
 
class Sum{
    int x = 10;
    int y = 20;
    public void print(){
        System.out.println("x="+x+", y="+y);
        System.out.println("sum = "+(x+y));
    }
}
 
interface InterSum {
    public abstract void print();
}

-       익명클래스는 객체를 생성하는 문장 뒤에 괄호를 사용하여 { } 일회용 클래스를 작성할 수 있다. 그러면 해당 객체를 상속받은 일회용 클래스가 되는 것이다.

-       클래스뿐만 아니라 일회용 클래스를 이용하여 인터페이스를 구현할 수도 있다. 이 또한 해당 인터페이스를 구현한 일회용 클래스가 되는 것이다.

-       위의 예제에서 Sum클래스를 익명클래스를 사용하여 출력을 해보았다. new Sum()옆부분에 { } 사용 일회용으로 사용할 클래스 내용을 작성하면 된다. 그러면 기존의 Sum클래스를 상속받은 일회용 클래스가 되는 것이다.