DEV Community

dss99911
dss99911

Posted on Originally published at dss99911.github.io

Java Stream과 Lambda 완벽 가이드

Java Stream과 Lambda

Java 8에서 도입된 함수형 프로그래밍 기능인 Stream API와 Lambda 표현식을 살펴봅니다.

Lambda 표현식

기본 문법

(argument-list) -> {body}
Enter fullscreen mode Exit fullscreen mode

인터페이스 구현

익명 클래스 대신 Lambda를 사용하여 간결하게 표현할 수 있습니다.

interface Drawable {
    public void draw();
}
Enter fullscreen mode Exit fullscreen mode

익명 클래스:

Drawable d = new Drawable() {
    public void draw() {
        System.out.println("Drawing " + width);
    }
};
Enter fullscreen mode Exit fullscreen mode

Lambda:

Drawable d2 = () -> {
    System.out.println("Drawing " + width);
};
Enter fullscreen mode Exit fullscreen mode

다중 파라미터

interface Addable {
    int add(int a, int b);
}

public class LambdaExpressionExample {
    public static void main(String[] args) {
        // 타입 추론 사용
        Addable ad1 = (a, b) -> (a + b);
        System.out.println(ad1.add(10, 20));  // 30

        // 명시적 타입 선언
        Addable ad2 = (int a, int b) -> (a + b);
        System.out.println(ad2.add(100, 200));  // 300
    }
}
Enter fullscreen mode Exit fullscreen mode

forEach 루프

List<String> list = new ArrayList<>();
list.add("ankit");
list.add("mayank");
list.add("irfan");
list.add("jai");

list.forEach(n -> System.out.println(n));
Enter fullscreen mode Exit fullscreen mode

메서드 참조

정적 메서드 참조

ContainingClass::staticMethodName
Enter fullscreen mode Exit fullscreen mode
interface Sayable {
    void say();
}

public class MethodReference {
    public static void saySomething() {
        System.out.println("Hello, this is static method.");
    }

    public static void main(String[] args) {
        Sayable sayable = MethodReference::saySomething;
        sayable.say();
    }
}
Enter fullscreen mode Exit fullscreen mode

스레드에서 활용:

Thread t2 = new Thread(MethodReference::ThreadStatus);
t2.start();
Enter fullscreen mode Exit fullscreen mode

다중 파라미터:

class Arithmetic {
    public static int add(int a, int b) {
        return a + b;
    }
}

BiFunction<Integer, Integer, Integer> adder = Arithmetic::add;
int result = adder.apply(10, 20);  // 30
Enter fullscreen mode Exit fullscreen mode

인스턴스 메서드 참조

containingObject::instanceMethodName
Enter fullscreen mode Exit fullscreen mode
interface Sayable {
    void say();
}

public class MethodReference {
    public void saySomething() {
        System.out.println("Hello, this is non-static method.");
    }

    public static void main(String[] args) {
        MethodReference methodReference = new MethodReference();
        Sayable sayable = methodReference::saySomething;
        sayable.say();

        // 익명 객체 사용
        Sayable sayable2 = new MethodReference()::saySomething;
        sayable2.say();
    }
}
Enter fullscreen mode Exit fullscreen mode

생성자 참조

ClassName::new
Enter fullscreen mode Exit fullscreen mode
interface Messageable {
    Message getMessage(String msg);
}

class Message {
    public Message(String msg) {
        System.out.print(msg);
    }
}

public class ConstructorReference {
    public static void main(String[] args) {
        Messageable hello = Message::new;
        hello.getMessage("Hello");
    }
}
Enter fullscreen mode Exit fullscreen mode

Stream flatMap

map 연산이 하나의 요소를 하나의 요소로 변환하는 반면, flatMap은 하나의 요소를 여러 개의 요소(또는 0개)로 변환할 수 있습니다.

계층 구조 예시

class Foo {
    String name;
    List<Bar> bars = new ArrayList<>();

    Foo(String name) {
        this.name = name;
    }
}

class Bar {
    String name;

    Bar(String name) {
        this.name = name;
    }
}
Enter fullscreen mode Exit fullscreen mode

데이터 생성

List<Foo> foos = new ArrayList<>();

// Foo 객체 생성
IntStream
    .range(1, 4)
    .forEach(i -> foos.add(new Foo("Foo" + i)));

// Bar 객체 생성
foos.forEach(f ->
    IntStream
        .range(1, 4)
        .forEach(i -> f.bars.add(new Bar("Bar" + i + " <- " + f.name))));
Enter fullscreen mode Exit fullscreen mode

flatMap 사용

foos.stream()
    .flatMap(f -> f.bars.stream())
    .forEach(b -> System.out.println(b.name));

// 출력:
// Bar1 <- Foo1
// Bar2 <- Foo1
// Bar3 <- Foo1
// Bar1 <- Foo2
// Bar2 <- Foo2
// Bar3 <- Foo2
// Bar1 <- Foo3
// Bar2 <- Foo3
// Bar3 <- Foo3
Enter fullscreen mode Exit fullscreen mode

3개의 Foo 객체가 9개의 Bar 객체로 변환됩니다.

한 줄로 표현하기

IntStream.range(1, 4)
    .mapToObj(i -> new Foo("Foo" + i))
    .peek(f -> IntStream.range(1, 4)
        .mapToObj(i -> new Bar("Bar" + i + " <- " + f.name))
        .forEach(f.bars::add))
    .flatMap(f -> f.bars.stream())
    .forEach(b -> System.out.println(b.name));
Enter fullscreen mode Exit fullscreen mode

Optional의 flatMap

flatMapOptional 클래스에서도 사용할 수 있어 null 체크를 우아하게 처리할 수 있습니다.

중첩 구조 예시

class Outer {
    Nested nested;
}

class Nested {
    Inner inner;
}

class Inner {
    String foo;
}
Enter fullscreen mode Exit fullscreen mode

기존 방식 (null 체크)

Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}
Enter fullscreen mode Exit fullscreen mode

Optional flatMap 사용

Optional.of(new Outer())
    .flatMap(o -> Optional.ofNullable(o.nested))
    .flatMap(n -> Optional.ofNullable(n.inner))
    .flatMap(i -> Optional.ofNullable(i.foo))
    .ifPresent(System.out::println);
Enter fullscreen mode Exit fullscreen mode

flatMap 호출은 객체가 존재하면 해당 객체를 감싸는 Optional을, 없으면 빈 Optional을 반환합니다.

정리

개념 설명
Lambda 익명 함수를 간결하게 표현
메서드 참조 기존 메서드를 Lambda 대신 사용
생성자 참조 생성자를 메서드 참조처럼 사용
map 1:1 변환
flatMap 1:N 변환 (스트림 평탄화)
Optional.flatMap null-safe한 중첩 객체 접근

Originally published at https://dss99911.github.io

Top comments (0)