[Java] Java8, 스트림(Streams)

A java.util.Stream 클래스는 동일한 타입의, 다수의 데이터에 대해 다양한 연산을 빠르게 적용할 수 있는 기능을 제공합니다. 스트림은 java.util.Collection 객체로부터 생성되는데, 리스트나 집합 등이 그 예인데요. 맵은 지원하지 않습니다. 다음과 같은 리스트가 있다고 하고 이 리스트를 통해 스트림을 만들어 다양한, 유용한 연산의 예제 코드를 언급하겠습니다.

List stringCollection = new ArrayList<>();
		
stringCollection.add("신숙주");
stringCollection.add("김옥자");
stringCollection.add("조미현");		
stringCollection.add("송기숙");
stringCollection.add("최미라");
stringCollection.add("박미남");
stringCollection.add("김구라");
stringCollection.add("김형준");

filter

요소 중 ‘김’으로 시작하는 문자열을 걸러내어 화면에 표시합니다.

stringCollection
    .stream()
    .filter((s) -> s.startsWith("김"))
    .forEach(System.out::println);

sorted

요소를 정렬하고, 정렬된 리스트에 대해 ‘김’자로 시작하는 요소를 화면에 표시합니다.

stringCollection
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("김"))
    .forEach(System.out::println);

map

요소 각각에 함수를 호출합니다. 아래는 각 요소의 뒤에 ‘씨’ 자를 붙이고 정렬한 후 출력합니다.

stringCollection
    .stream()
    .map((a) -> a + "씨")
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

count

‘김’자로 시작하는 요소의 개수를 얻습니다.

long cntKim = stringCollection
    .stream()
    .filter((s) -> s.startsWith("김"))
    .count();

reduce

각 요소를 하나로 묶습니다.

Optional reduced = stringCollection
    .stream()
    .sorted()
    .reduce((s1, s2) -> s1 + "," + s2);

reduced.ifPresent(System.out::println);

anyMatch, allMatch, noneMatch

각 요소에 대해 지정한 조건에 부합하는지를 검사합니다. 첫번째인 anyMatch는 ‘김’으로 시작하는 요소가 하나라도 있다면 참입니다. 두번째인 allMatch는 모든 요소가 ‘김’으로 시작해야만 참입니다. 끝으로 세번째는 ‘고’로 시작하는 요소가 하나도 없어야 ‘참’입니다.

boolean anyStartsWithKim = stringCollection
    .stream()
    .anyMatch((s) -> s.startsWith("김"));

boolean allStartsWithKim = stringCollection
    .stream()
    .allMatch((s) -> s.startsWith("김"));

boolean noneStartsWithGo = stringCollection
    .stream()
    .noneMatch((s) -> s.startsWith("고"));

[Java] Java8, 람다 표현식(Lambda expressions)

많은 프로그래밍 언어에서 람다 표현식은 한번 사용하고 버릴 함수를 정의하기 위한 용도로 많이 사용됩니다. 물론 이 람다 표현식에 의해 만들어진 함수를 변수에 담아 두번 이상 사용할 수도 있지만… 어찌되었든, 람다는 빠르게 함수를 만들어 사용하고는, 버리는 용도로 사용합니다.

Java 8에서 람다 표현식을 지원하고 있는데요. 문자열 요소를 가지는 리스트를 정렬하는 예를 통해 람다 표현식을 살펴 보겠습니다. 정렬할 문자열 리스트로 사용할 names 객체 변수는 아래와 같습니다.

List names = 
    Arrays.asList("검소", "겸손", "명상", "욕심", "탐하는 마음", "경솔", "교만", "어질지못함");

람다 표현식이 아닌 우리가 이미 알고 있는 방식의 코드는 아래와 같습니다.

Collections.sort(names, new Comparator() {
    @Override
    public int compare(String a, String b) {
        return -b.compareTo(a);
    }
});

위의 Collections의 sort 함수의 두번째 인자에 1회용 목적으로 익명 객체를 생성해 던져주고 있습니다. 이 익명 객체를 생성하기 위한 인터페이스가 단 하나의 매서드에 대한 정의를 필요로 합니다. 이를 람다 표현식으로 바꾸면 아래와 같습니다.

Collections.sort(names, (String a, String b) -> {
    return -b.compareTo(a);
});

람다식을 컴파일로가 해석할 때 어떤 타입의 어떤 매서드를 정의하는지를 자동으로 추론해 줍니다. (염병할, 나보다 더 똑똑해!) 위의 람다식을 보면 함수의 코드가 한줄이므로 아래처럼 더 줄일 수 있습니다.

Collections.sort(names, (String a, String b) ->  -b.compareTo(a));

코드가 한줄이므로 {}을 제거할 수 있고, return 키워드를 제거해도 반환한다라는 개념을 이미 추론을 통해 파악하고 있습니다. (이제 니가 코딩해라, 난 뭐하냐?) 충분히 줄였다 싶을 람다 표현식을 더 줄일 수 있는데요. 어차피 어떤 함수를 람다식으로 정의하는지를 알고 있다면 해당 함수의 타입을 명시적으로 언급하지 않아도 추론이 가능할 것으므로 아래처럼 더 최적화할 수 있습니다.

Collections.sort(names, (a, b) -> -b.compareTo(a));

(이젠 나도 모르겠을 암호 코드.. ㅜ_ㅜ) 위의 내용은 람다식의 일부입니다. 람다에 좀더 깊이 들어가면.. 깜짝 놀라거임.. -O-;