자바(SE)

[람다표현식]inner class

  • -

이번 포스트에서는 람다 표현식에 대해서 살펴보자. 람다라는 이상한 녀석을 알기 위해서는 왜 람다식이 필요한지 알아야 하는데 그 기원은 inner class에서 찾아볼 수 있다.

 

Inner Class

 

inner class의 필요성

만약 우리가 SmartPhone 이라는 클래스를 만든다고 생각해보자. SmartPhone은 Battery, WifiModule, GpsModule 등 다양한 클래스들을 has-a 관계로 구축되어있을 것이다.

일반적으로 파일 하나 당 하나의 Java Class를 구성하므로 아래와 같은 클래스가 구성될 것이다.

public class SmartPhone1 {
  private Battery battery = new Battery();
  private GpsModule gps = new GpsModule(battery);
  private WiFiModule wifi = new WiFiModule();
}

public class Battery{. . .}

public class GpsModule{
  Battery battery;
  public GpsModule(Battery battery) {
    this.battery = battery;
  }
  public void useBattery() {
    battery.useBattery(5);
  }
}

public class WifiModule{...}

그런데 SmartPhone에서 Battery, GpsModule등을 따로 떼어서 사용하는 (물론 그런 금손이 있을 수도 있지만..)경우는 거의 없다. 즉 SmartPhone에서 사용되는 Battery, GpsModule 등은 SmartPhone과는 아주 밀접하지만 다른 곳에서는 사용될 일이 없는 상황이다.

그리고 GpsModule이 동작하기 위해서는 Battery가 필요한데 Battery는 SmartPhone이 가지는 내부의 private 자원이다. 따라서 GpsModule을 생성하는 과정에서 Battery에 대한 참조를 전달해야 하고 여러 파일을 쫒아다니며 분석해야 하므로 프로그램의 복잡도가 높아진다.

이처럼 모듈화는 필요한데 이 모듈이 외부에서 사용되지 않는 상황에서 복잡도를 낮추기 위해서 inner class를 사용할 수 있다.

 

inner class

inner class는 외부 클래스(outer class) 내부에 작성하는 클래스를  말하며 inner class와 outer class간에 자유로운 멤버 접근이 가능하다. (private 멤버 포함) 또한 하나의 outer class 내부에 작성되기 때문에 다른 곳에서 사용되지 않는 클래스를 감춤으로써 복잡도를 감소시킬 수 있다.

 

inner class의 종류

inner class는 선언 위치, static 제한자여부 등에 따라 4가지로 구분된다.

선언 위치 종류 특징
멤버 영역 instance inner class 외부 클래스의 멤버 변수처럼 사용됨
주로 외부 클래스의 인스턴스 멤버들과 관련된 작업 수행
static inner class static이 붙은 inner class
주로 외부 클래스의 static 멤버들과 관련된 작업 수행
로컬 영역 local inner class 외부 클래스의 메서드나 초기화 블록에서 선언됨
선언된 영역 내부에서만 사용 가능
anonymous inner class 이름이 없으며 클래스 선언과 객체 생성을 동시에 처리하는 형태
주로 객체를 재사용하지 않고 한번만 사용할 때

 

간단히 inner class 가 작성되는 형태를 살펴보자.

public class OuterClass {
    class InstanceInnerClass{}
    static class StaticInnerClass{}
    public static void main(String[] args) {
        class LocalInnerClass{}
        // anonymous inner class: Runnable은 interface이므로 new를 사용할 수 없다.
        new Runnable() {
            public void run(){}
        };
    }
}

 

anonymous inner class

 

anonymous inner class 작성

anonymous inner class(익명의 inner class)는 local inner class의 한 형태로 글자 그대로 이름이 없는 inner class를 뜻한다. 위에서 작성된 문장에서 anonymous inner class가 사용된 예를 살펴보자.

new Runnable() {
    @Override
    public void run(){}
};

별 문제가 없어보이지만 사실은 상당히 독특하다. 일단 Runnable은 interface이다. 따라서 new 키워드를 통해서 객체를 생성할 수 없다. 그리고 생성자 처럼 호출한 new Runnable() 뒤에 느닷없는 중괄호가 등장했다. 중괄호 내부에는 Runnable interface에 선언된 run 메서드를 재정의 한다.

일반적으로 Runnable interface를 구현하는 클래스라면 아래와 같이 작성해야 했을 것이다.

class ClassName implements Runnable{
    @Override
    public void run(){}
}

그런데 여기서 ClassName 에 해당하는 부분이 생략된 형태이기 때문에 익명의 inner class라고 부른다.

 

용도

anonymous inner class는 위의 예처럼 선언과 동시에 클래스를 구성하고 객체를 생성해서 사용까지 처리하는 형태이다. 주요 용도는 abstract class나 interface를 구현해서 사용해야 하는데 1회용으로 더 이상 재사용하지 않는 경우에 주로 사용된다.

다음은 SomeSpeaker 타입의 객체를 사용하는 useSpeaker 라는 메서드에 SomeSpeaker를 전달하는 두 가지 형태를 보여준다.

public static void useSpeaker(SomeSpeaker speaker){
    speaker.sayHello();
}

 

 

이렇게 까지 해야했을까?

위와 같이 일반 클래스를 작성했을 때 보다 anonymous inner class를 이용했을 때의 코드가 좀 더 간결해보일 수 있다. 

그런데..

useSpeaker라는 메서드를 사용하면서 정말 우리가 필요했던 것은 SomeSpeaker의 참조값이었는지 생각해보자. 

사실 useSpeaker가 정말로 필요한 것은 Speaker를 통해서 인사를 하는 행위였다! 즉 sayHello라는 기능이 필요했던 것이다. 하지만 Java에서는 이 기능만을 딱 뜯어서 전달할 수 있는 방법은 없다. 그래서 어쩔 수 없이 객체를 만들어야 하는데 인터페이스는 객체를 만들 수 없으니까 대충 클래스를 만들고 객체화 해서 전달했던 것이다.

이런 고민에 대한 답을 함수형 프로그래밍에서 찾을 수 있는데 anonymous inner class를 함수형 프로그래밍 형태로 작성하는 것이 람다 표현식이다. 위 코드를 람다식으로 작성하면 아래와 같이 표현할 수 있다.

useSpeaker(()-> System.out.println("안녕")));

아주 깔끔하고 의도에 대한 파악이 쉬운 표현식이 작성되었다. 이제 본격적으로 람다식의 작성법을 살펴보자.

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.