내부 클래스

클래스 안에 클래스가 들어갈 수도 있구나

클래스 안의 클래스

digraph netsted_class {
node [shape=box];
"Nested Class" -> "Static Nested Class";
"Nested Class" -> "Inner Class";
"Inner Class" -> "Local Inner Class";
"Inner Class" -> "Annonymous Inner Class";
}

Nested 클래스 필요성

  • 한 곳에서만 사용되는 클래스를 논리적으로 묶어서 처리하고 싶을 때
  • 캡슐화(encapsulation)가 필요할 때, 예를 들면 A라는 클래스의 멤버들을 B라는 클래스만 접근하게 하고 싶을 때 B라는 클래스를 A 클래스에 숨기는 기능을 할 수 있다.
  • 소스의 가독성과 유지 보수를 효율적으로 하기 위해

Static nested 클래스의 특징

package c.inner;

class OuterOfStatic {

    public OuterOfStatic() {
    }

    private static class StaticNested {
        private int val = 0;

        public StaticNested() {
            System.out.println("Nested static class");
        }

        public int getValue() {
            new OuterOfStatic();
            return val;
        }

        public void setValue(int val) {
            this.val = val;
        }
    }
}

StaticNested 클래스 객체 사용하는 방법을 알아보자.

package c.inner;

public class NestedSample {
    public static void main(String[] args) {
        NestedSample sample = new NestedSample();
        sample.makeStaticNestedObject();
    }

    public void makeStaticNestedObject() {
        OuterOfStatic.StaticNested staticNested = new OuterOfStatic.StaticNested();
        staticNested.setValue(3);
        System.out.println(staticNested.getValue());
    }
}

감싸고 있는 클래스 OuterOfStatic 이름 뒤에 점을 찍고 static 클래스 이름을 쓰고 객체를 만든다. 객체가 만들어진 후에 사용하는 방법은 일반 클래스와 같다.

내부 클래와 익명 클래스

package c.inner;

public class OuterOfInner {
    class Inner {
        private int val =0;

        public int getValue() {
            return val;
        }

        public void setValue(int val) {
            this.val = val;
        }
    }
}

내부 클래스(inner class)를 사용하는 방법을 알아보자.

package c.inner;

public class NestedSample {
    public void makeInnerObject() {
        OuterOfInner outer = new OuterOfInner();
        OuterOfInner.Inner inner = outer.new Inner();
        inner.setValue(3);
        System.out.println(inner.getValue());
    }

    public static void main(String[] args) {
        NestedSample sample = new NestedSample();
        sample.makeInnerObject();
    }
}

Inner 클래스의 객체를 생성하기 전에 먼저 Inner 클래스를 포함하고 있는 OuterOfInner 클래스의 객체를 만들어야 한다. 그리고 그 객체를 통해서 outer.new 라는 예약어를 통해서 Inner 클래스의 객체를 만들어 낼 수 있다.

익명 클래스(anonymous class)

GUI(Graphic User Interface)에서 리스너(Listener)를 처리할 때 익명클래스를 많이 이용한다. 리스너란 키보드를 눌렀을 때 이벤트(Event)가 발생하는데 그러한 이벤트를 기다리고 있다가 발생되면 처리하는 클래스이다.

package c.inner;

public class MagicButton {
    private EventListener listener;

    public MagicButton() {
    }

    public void setListener(EventListener listener) {
        this.listener = listener;
    }

    public void onClickProcess() {
        if (listener != null) {
            listener.onClick();
        } else {
            System.out.println("should set listener");
        }
    }
}

EventListener 인터페이스는 다음과 같다.

package c.inner;

public interface EventListener {
    public void onClick();
}

클래스를 적용해 보자. 버튼을 누르는 과정을 모의실험해본다. onClickProcess()메소드는 실제 클래스에서는 만들지 않는다. 버튼을 클릭하는 행위를 대신하기 위해 만들어진 임시 메소드이다.

package c.inner;

public class NestedSample {
    //중간 생략
    public void setButtonListener() {
        MagicButton button = new MagicButton();
        button.setListener(???);
        button.onClickProcess();
    }
}

???안에 넣어야 할 리스너를 다음과 같이 클래스를 만들어서 넣을 수 있다.

package c.inner;

public class NestedSample {
    //중간 생략
    class MagicButtonListener implements EventListener {
        public void onClick() {
            System.out.println("Magic Button Clicked!!!");
        }
    }
}

다음으로 setButtonListener() 안에 EventListener의 객체를 생성해서 넣어준다.

package c.inner;

public class NestedSample {
    //중간 생략
    public void setButtonListener() {
        MagicButton button = new MagicButton();
        MagicButtonListener listener = new MagicButtonListener();
        button.setListener(listener);
        button.onClickProcess();
    }
}

위와 같이 리스너를 내부 클래스로 만들어 사용할 수도 있지만 익명 클래스로 만들어서 사용할 수 있다. 익명 클래스를 사용할 때는 다른 곳에서 필요로하지 않을 때 사용한다. 즉 전용으로 사용하고자 할 때 사용하면 된다.

package c.inner;

public class NestedSample {
    //중간 생략
    public void setButtonListener() {
        MagicButton button = new MagicButton();
        // MagicButtonListener listener = new MagicButtonListener();
        // button.setListener(listener);
        button.setListener(new EventListener() {
            public void onClick() {
                System.out.println("Magic button clicked !!!");
            }
        });
        button.onClickProcess();
    }
}

setListener() 메소드 안에 EventListener를 새로 생성하면서 동시에 onClick()메소드를 구현하고 있는 것을 알 수 있다. 새로 생성된 클래스는 이름을 지정하지 않고 사용하기 때문에 익명 클래스라고 한다. 익명 클래스는 이름이 없기 때문에 다른 곳에서 참조를 할 수 없다. 만약 다른 곳에서 같은 객체를 재사용하려면 다음과 같이 사용한다.

package c.inner;

public class NestedSample {
    //중간 생략
    public void setButtonListener() {
        MagicButton button = new MagicButton();
        // MagicButtonListener listener = new MagicButtonListener();
        // button.setListener(listener);
        EventListener listener = new EventListener() {
            public void onClick() {
                System.out.println("Magic button clicked !!!");
            }
        };
        button.setListener(listener);
        button.onClickProcess();
    }
}

Nested 클래스의 특징은 꼭 알아야 한다

package c.inner;

public class NestedValueReference {
    public int publicInt = 0;
    protected int protectedInt = 1;
    int justInt = 2;
    private int privateInt = 3;
    static int staticInt = 4;

    static class StaticNested {
        public void setValue() {
            staticInt = 14;
        }
    }

    class Inner {
        public void setValue() {
            publicInt = 20;
            protectedInt = 21;
            justInt = 22;
            privateInt = 23;
            staticInt = 24;
        }
    }

    public void setValue() {
        EventListener listener = new EventListener() {
            public void onClick() {
                publicInt = 30;
                protectedInt = 31;
                justInt = 32;
                privateInt = 33;
                staticInt = 34;
            }
        };
    }
}

Nested static 클래스 안에서는 static 변수만 참조할 수 있다.