Dev

뒤늦게 배워보자, Java 8 Part. 1

December 28, 2017

author:

뒤늦게 배워보자, Java 8 Part. 1

람다 표현식

동작 파라미터화(혹은 행위 매개변수화, behavior parameterization)

동작 파라미터화(behavior parameterization)란 어떤 형태로 실행될지 결정되지 않은 코드 블록을 의미한다. 동작 파라미터화에서 사용되는 코드 블록의 실행은 미뤄진다.

동작 파라미터화를 왜 사용하는가?

동작 파라미터화를 사용하는 이유는 요구사항 변화에 더 유연하게 대응할 수 있기 때문이다. 동작 파라미터를 코드에서 사용하기 위해선 인터페이스를 선언하고 특정 동작을 수행하는 코드를 구현하면 된다. 대표적인 예로, 프레디케이트(predicate)라는 boolean을 반환하는 메서드(혹은 메서드)를 사용하는 방법을 아래에서 소개하고 있다.


interface GundamPredicate{
    public boolean test(MobileSuite mobileSuite);
}

static class FirstGundamPredicate implements GundamPredicate {
    @Override
    public boolean test(MobileSuite mobileSuite) {
        return "RX-78".equals(gundam.getModelNumber());
    }
}

메서드가 다양한 동작(또는 전략)을 받아서 내부적으로 다양한 동작을 수행할 수 있도록 구성하면 된다. 동작 파라미터화를 사용할 때 가장 많이 사용하는 방법은 익명 클래스를 사용하는 방법이다. 그러나 익명 클래스를 사용하게 되면 코드가 장황(verbosity)하게 길어지는 특징이 있다.


// 가독성에 좋지 않음
List<Gundam> firstGundams = filterGundams(inventory, new FirstGundamPredicate() {
    public boolean test(MobileSuite mobileSuite) {
        return "RX-78".equals(gundam.getModelNumber());
    }
});

람다(Lambda)

익명 클래스로 다양한 동작을 구현할 수 있지만, 만족할 만큼 코드가 깔끔하지는 않다. 깔끔하지 않은 코드는 동작 파리미터를 실전에 적용하는 것을 막는다. Java 8에서는 가독성을 높이면서 유연성을 확보하기 위해서 익명 클래스처럼 이름이 없는 함수이자 메서드 인수로 전달할 수 있는 람다 표현식을 도입했다. 즉, 람다(lambda) 표현식을 정의하자면 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다.

람다 표현식의 대표적인 특징은 일반적인 메서드와 달리 이름이 없으며, 특정 클래스에 종속되지 않기 때문에 ‘메서드’가 아니라 함수라고 부른다. 또한 람다 표현식은 메서드 인수로 전달하거나 변수로 전달할 수 있으며, 익명 클래스처럼 습관적인 코드를 구현할 필요가 없다.


// 익명함수
Comparator<MobileSuite> byYear = new Comparator<MobileSuite>() {
    public int compare(MobileSuite m1, MobileSuite m2) {
        return m1.getYear().compareTo(m2.getYear());
    }
};

// 람다 표현식
Comparator<MobileSuite> byYear = (MobileSuite m1, MobileSuite m2) -> m1.getYear().compareTo(m2.getYear());

람다 표현식에서 (MobileSuite m1, MobileSuite m2)람다 파라미터라고 하고, ->화살표(arrow)라 하며, m1.getYear().compareTo(m2.getYear())람다 바디라 한다.

Java 8에서는 5가지 형태의 람다 표현식을 지원한다.


(String s) -> s.length() // return이 함축되어 있음
(String s) -> s.length() > 10
(String s1, String s2) -> { // return이 없음(void)
    System.out.println("s1 + s2 length");
    System.out.println(s1.length() + s2.length());
}
(String s1, String s2) -> s1.length().compareTo(s2.length());
() -> "hello!" // 파라미터가 없음

함수 인터페이스(Functional Interface) 사용법

함수형 인터페이스(Functional Interface)는 ‘하나의 추상 메서드’만 가지고 있는 인터페이스이다. 대표적인 함수형 인터페이스는 Comparator, Runnable등이 있다. 함수형 인터페이스의 추상 메서드의 시그너처(signature)는 람다 표현식의 시그너처를 가리킨다. 람다 표현식의 시그너처를 서술하는 메서드를 함수 디스크립터(function descriptor)라고 부른다. 따라서 추상 메서드의 시그너처와 함수 디스크립터가 같다면, 함수형 인터페이스를 활용 할 수 있다.


public static void process(Runnable r) {
    r.run();
}

Runnable r1 = new Runnable() {
    public void run() {
        System.out.println("Hello World");
    }
};

Runnable r2 = () -> System.out.println("Hello World");

process(r1);
process(r2);
process(() -> System.out.println("Hello World"))

그렇다면, “왜 함수형 인터페이스를 인수로 받는 메서드에만 람다 표현식을 사용할 수 있는 걸까?”라는 의문이 생길텐데, 대부분의 자바 프로그래머가 하나의 추상 메서드를 갖는 인터페이스에 이미 익숙하다는 점이 가장크게 작용했을 것이다.

대표적인 함수형 인터페이스

  • Predicate
    • test라는 추상 메서드를 정의하며, test는 <T>의 객체를 인수로 받아 boolean을 반환한다.
  • Consumer
    • accept라는 추상 메서드를 정의한다. <T> 형식의 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 Consumer 인터페이스를 사용할 수 있다.
  • Function
    • apply라는 추상 메서드를 정의한다. <T>를 인수로 받아서 제네릭 형식 <R> 객체를 반환하는 apply 라는 추상 메서드를 정의한다. 입력을 출력으로 매핑하는 람다를 정의할 때 Function 인터페이스를 활용할 수 있다.

지역 변수 사용

람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수(free variable)를 활용할 수 있다. 이와 같은 동작을 람다 캡처링이라고 부른다.


int portNumber = 137;
Runnable r = () -> System.out.println(portNumber);

람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처(자신의 바디에서 참조할 수 있음)할 수 있다. 하지만 그러려면 지역 변수는 명시적으로 final로 선언되어 있어야 하거나, 실질적으로 final로 선언된 변수와 똑같이 사용되어야 한다. 즉 람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡처할 수 있다. 이러한 이유는 자유 지역 변수의 복사본을 제공하기 때문에 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴 것으로 파악할 수 있다.

메서드 레퍼런스

메서드 레퍼런스란 기존의 메서드를 정의를 재활용해서 람다처럼 전달할 수 있다.


(String s) -> s.length() ==> String::length
() -> Thread.currentThread().dumpStack() ==> Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) ==> String::substring
(String s) -> System.out.println(s) ==> System.out::println


Ref.

  1. 자바 8 인 액션
  2. Functional Programming in Java 8
  3. 모던 자바 (자바8) 못다한 이야기, 케빈 TV

가상 Data Class

@Data
class MobileSuite {
    private String classification;
    private String modelNumber;
    private String officialName;
    private Manufacture manufacture;
    private Pilot pilot;
}

@Data
class Pilot {
    private String name;
    private int birthDate;
    private String gender;
    private String geneticType;
}

@Data
class Manufacture {
    private String name;
    private int established;
    private String purpose;
}