Java8에서는 Lambda 식과 같은 함수형 프로그래밍을 지원하기 위한 API를 포함해서 이를 위한 특징적인 API 들이 많이 생겨났다.

그 중에서도 이 포스팅에서는 핵심적인 3가지, Stream, Optional, Lambda 에 대해 정리하고자 한다.


 먼저 컬렉션을 다루기위한 API 중에는 Stream API 가 존재한다.

Java 에서 컬렉션은 데이터를 관리하고 제어하기 위한 좋은 컨테이너이지만 구현의 인터페이스 적인 측면에 있어서는 깔끔하다고 보기는 힘들다.


특히 이것은 SQL 문과 비교해보면 이해가 쉽다. 학생 정보를 담고 있는 데이터 컬렉션에서 특정 성적 이상을 가진 학생들을 찾을 때 Collection을 이용하면 다음과 같이 구현할 수 있다.


List<Student> students = getStudentList();
List<Student> List = new ArrayList();
For(Student s : students)
{
    If(s.getGrade() >= 80)
        List.add(s);
}


반면, Sql 문을 사용한다면 이는 조건절이 포함된 SELECT 구문 하나로 처리가 가능하다.

Java8의 Stream API는 이러한 복잡한 구조의 데이터 처리를 간단하게 해주어 복잡도를 낮추는 설계가 가능하게 한다. 또한 이런 종류의 Lazy collection 은 멀티 스레드 및 병렬처리 환경에서도 개발자에게 상당히 유연한 선택지를 갖게 한다. 또한 한번만 소비 가능한 형태의 컬렉션이기 때문에 메모리 관리 차원에서의 이점도 있다.



List<Student> list = students.stream().filter(t->t.getGrade()>=80).collect(Collectors.toList());


Stream API 는 위와 같이 Collection 의 각 요소들을 따로따로 주어진 함수구문(위에서는 람다식)에 따라 처리하며 내부 버퍼를 통해 주어진 형태로 반환해준다.


Stream API 에는 위에 적용된 filter 나 collector 와 같은 메서드를 포함해서, map, flatMap, reduce, peek 등 다양한 메서드를 제공한다.

Stream API 의 자세한 사용에 있어서는 추가로 포스팅하고자 한다.


 새로 추가된 Optional API 는 Java8 에서 제공하는 Null이 포함되지 않는 Collection이다.

NullPointerException 을 걱정하지 않아도 되며, ifpresent(함수식) 기능을 통해 무결성 검증과 동시에 함수식을 수행할 수도 있다. 

가령 NULL 이 될 수 있는 객체에 대해 다음과 같이 핸들링 할 수 있다. 



public Student getStudentById(String studentId) {
// Stream API 의 findFirst() 는 그 자체로 Optional 을 return 하기 때문에 사실 이는 좀 어색한 함수 구현이다.
return getStudentList().stream()
.filter(student->student.getStudentId().equals(studentId)).findFirst().get();
}

public void foo() {

Student student = getStudentById("철수");

if(Optional.ofNullable(student).isPresent()) {
System.out.println("철수라는 학생이 존재합니다.");
} else {
System.out.println("철수라는 학생은 없습니다.");
}
}


위와 같이 Optional 을 사용하면 Null 을 핸들링할 수 있는 객체를 다룰 수 있다. if(student == null) 과 같은 형태 대신 좀 더 직관적으로 다양한 이용이 가능하다.


마지막으로 Java8의 람다식은 함수형 인터페이스를 제공함으로써 지원이 된다.

 함수형 인터페이스를 만드는 방법은 @FunctionalInterface 어노테이션을 이용해 명시적으로 생성도 가능하지만, method가 하나 존재하는 인터페이스를 선언함으로써 생성이 가능하다.

다음의 예시를 참조할 수 있다.



Public interface Foo
{
    Int calc(int a, int b);
}
{
    Foo add = (int a, int b) -> { return a+b; };
    Foo minus = (int a, int b) -> { return a-b; };
    Int addv = add.calc(3, 5);      //8
    Int minusv = minus.calc(5, 3);  //2
}


위의 예시는 직접 FunctionalInterface 를 구현하여 Lambda 식을 적용해본 것이고, 실제 많이 사용되는 것은 Java8 에서 제공하는 API 들이다. 다음은 그 몇가지 종류이다.


Function<T, R> : T 타입의 입력파라미터를 받아 R 타입을 리턴한다.


Supplier<T> : void 타입의 파라미터를 입력받아 T 타입을 리턴한다.


Consumer<T> : T 타입의 파라미터를 받아 void 를 리턴한다.


Predicate<T> : T 타입의 입력을 받아 boolean 을 리턴한다.


BiPredicate<T, U> : T와 U 타입을 입력받아 boolean 을 리턴한다.


UnaryOperator<T, T> : T 타입 2개를 입력받아 T 타입을 리턴한다.


이 외에도 많은 종류의 FunctionalInterface 를 제공하며, 잘 사용하면 함수형 프로그래밍의 장점을 누릴 수 있다.


+ Recent posts