HOWTO · Java
Java의 화살표 연산자 ->
Java의 `->` 구문이 무엇을 의미하는지, 함수형 인터페이스와 컬렉션, 스트림, 스레드에서 람다 표현식을 어떻게 활용하는지 알아봅니다.
이 페이지의 내용
Java에서 -> 구문이 무엇을 의미하는지, 함수형 인터페이스와 컬렉션, 스트림, 스레드에서 람다 표현식을 어떻게 활용하는지 알아봅니다.
Java에서 화살표 연산자가 의미하는 것
Java 8 이전에는 Runnable 작업을 run 메서드가 있는 익명 클래스로 작성할 수 있었습니다. 람다를 사용하면 같은 동작을 () -> System.out.println("Working")처럼 표현할 수 있습니다. 화살표는 여전히 람다 표현식의 일부이며, 대상 타입과 무관한 독립적인 함수 값을 만들지는 않습니다. 컴파일러는 변수 선언이나 메서드 인수처럼 주변 문맥을 바탕으로 함수형 인터페이스를 결정합니다.
람다 표현식의 구문과 규칙
람다는 매개변수 목록, 화살표, 본문으로 이루어집니다. x -> x * x처럼 표현식 본문에서는 중괄호를 생략할 수 있습니다. 블록 본문에는 중괄호를 사용하며, 대상 메서드가 값을 반환한다면 명시적인 return이 필요합니다. 매개변수가 없으면 ()를 사용하고, 여러 매개변수는 (left, right) -> left + right처럼 괄호로 묶습니다. (name) -> name.length()처럼 매개변수가 하나일 때도 괄호를 쓸 수 있습니다.
값을 반환하는 블록 본문 람다에서는 대상 메서드의 반환 타입이 결과 타입을 결정합니다. 다음 예제는 applyAsInt 메서드가 int를 반환하는 ToIntFunction<String>에 람다를 할당합니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
import java.util.function.ToIntFunction;
public class BlockLambdaExample {
public static void main(String[] args) {
ToIntFunction<String> length = (String name) -> {
return name.length();
};
System.out.println(length.applyAsInt("Mira"));
}
}
본문은 대상 메서드의 반환 타입과 검사 예외 규칙에 맞아야 합니다. 람다는 임의의 클래스나 서로 관련 없는 추상 메서드가 여러 개 있는 인터페이스에 할당할 수 없습니다. 기본 메서드와 정적 인터페이스 메서드는 이 규칙에서 추가 추상 메서드로 세지 않습니다.
함수형 인터페이스와 함께 람다 사용하기
람다를 작은 함수형 인터페이스에 할당한 뒤 해당 인터페이스의 메서드로 호출할 수 있습니다. 이 자체 완결형 예제는 main이 있는 클래스를 먼저 두어 바로 실행할 수 있게 구성했습니다. 출력에는 draw()가 출력하는 한 줄이 표시됩니다.
다음 예제를 실행해 보세요.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
public class ArrowExample {
@FunctionalInterface
interface Drawable {
void draw();
}
public static void main(String[] args) {
Drawable shape = () -> System.out.println("Drawing a circle");
shape.draw();
}
}
Drawing a circle
Java 컬렉션, 스트림, 스레드에서 자주 쓰는 방법
forEach 같은 컬렉션 메서드는 함수형 인터페이스를 받으므로, 람다로 각 요소에 수행할 작업을 지정할 수 있습니다. 이 예제에서 name은 현재 목록 요소이며, Consumer<String> 람다는 그 값을 출력합니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
import java.util.Arrays;
import java.util.List;
public class CollectionLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira");
names.forEach(name -> System.out.println(name));
}
}
스트림의 filter 메서드는 조건에 맞는 요소를 남기기 위해 Predicate<String> 람다를 받습니다. 이어지는 forEach 연산은 조건을 통과한 각 이름을 출력합니다.
import java.util.Arrays;
import java.util.List;
public class StreamLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira", "Bo");
names.stream()
.filter(name -> name.length() > 2)
.forEach(name -> System.out.println(name));
}
}
이 API들은 반복과 처리를 서로 다른 방식으로 표현하지만, 흐름을 따라가기 쉽다면 전통적인 루프가 더 적절할 수도 있습니다.
Runnable은 인수를 받지 않고 값을 반환하지 않는 추상 메서드 하나를 가지므로 스레드 작업에 흔히 사용됩니다. 이 람다가 Runnable 작업을 제공합니다. Thread를 만드는 것만으로 작업이 실행되지는 않으며, start()를 호출하면 독립적으로 실행되도록 예약됩니다.
public class ThreadLambdaExample {
public static void main(String[] args) {
Runnable task = () -> System.out.println("Task running");
Thread worker = new Thread(task);
worker.start();
}
}
스레드의 출력은 다른 동시 작업과 비교해 순서가 달라질 수 있으며, 스케줄링은 보장되지 않습니다. 대상 인터페이스와 주변 코드만으로 동작을 명확히 알 수 있을 때 람다를 사용하세요. 람다는 구문을 줄여 주지만 API나 동시성 동작을 이해해야 할 필요까지 없애지는 않습니다.