자바 8은 함수형 프로그래밍의 개념을 도입하여 람다 표현식과 스트림 API를 제공한다. 이 두 가지 기능은 코드의 가독성과 간결성을 높이고, 개발자가 복잡한 연산을 단순화하는 데 큰 도움을 준다. 이 글에서는 람다 표현식과 스트림의 개념, 특징, 사용 방법을 설명하고, 이를 활용한 코드 예제와 함께 자바 프로그래밍을 더욱 효율적으로 작성하는 방법을 알아본다.
1. 람다 표현식(Lambda Expression)
람다 표현식은 간단히 말해 익명 함수(anonymous function) 를 구현할 수 있는 문법이다. 기존에 자바에서 익명 클래스를 이용해 작성하던 함수를 간결하게 표현할 수 있으며, 불필요한 코드 작성량을 줄여 가독성을 높인다.
람다 표현식의 기본 문법은 다음과 같다:
// 기본 구조
(parameters) -> expression
// 두 정수를 더하는 람다 표현식
(int x, int y) -> x + y
람다 표현식은 함수형 인터페이스의 구현체를 제공하는데, 함수형 인터페이스는 단 하나의 추상 메서드만을 가지고 있는 인터페이스를 의미한다. 자바 표준 라이브러리에서는 Runnable, Callable, Comparator 등이 함수형 인터페이스의 예이다.
1.1. 람다 표현식의 장점
람다 표현식을 사용하는 주된 장점은 다음과 같다:
- 코드의 간결성: 람다 표현식을 사용하면 불필요한 코드가 줄어들어 간결하고 읽기 쉬운 코드를 작성할 수 있다.
- 익명 함수 사용: 이름 없는 함수를 만들어 특정 위치에서 바로 사용할 수 있으므로, 별도의 메서드나 클래스 생성 없이 쉽게 기능을 구현할 수 있다.
- 병렬 처리 및 함수형 프로그래밍 지원: 스레드, 병렬 처리와 같은 환경에서 코드 작성이 용이하며, 함수형 프로그래밍을 적극적으로 사용할 수 있다.1.2. 람다 표현식 예제람다 표현식은 기존의 익명 클래스를 대체하여 더욱 간단하게 작성할 수 있다. 다음 예제에서는 기존의 Runnable 인터페이스를 람다 표현식으로 변경하여 사용해 보자.
// 기존의 익명 내부 클래스를 사용하는 방식
Runnable classicRunnable = new Runnable() {
@Override
public void run() {
System.out.println("Classic Runnable");
}
};
// 람다 표현식을 사용하는 방식
Runnable lambdaRunnable = () -> {
System.out.println("Runnable with Lambda Expression");
};
위의 예제에서 Runnable 인터페이스를 사용하는 코드를 보면, 기존 익명 클래스를 이용한 방식보다 람다 표현식을 사용하는 방식이 훨씬 간결한 것을 확인할 수 있다.
2. 스트림(Stream)
스트림(Stream)은 자바 8에서 도입된 기능으로, 컬렉션 데이터를 처리하고 가공할 때 매우 유용하다. 스트림은 데이터를 반복적으로 처리하는 데 필요한 코드를 줄여주고, 다양한 연산을 통해 데이터를 필터링, 매핑, 정렬, 집계 등의 작업을 할 수 있도록 지원한다.
2.1. 스트림의 특징 및 사용법
- 데이터 소스를 기반으로 생성: 스트림은 배열, 컬렉션, I/O 채널 등 다양한 데이터 소스에서 생성할 수 있다. List나 Set 같은 컬렉션에서는 stream() 메서드를 호출하여 스트림을 생성한다.
List<String> myList = Arrays.asList("사과", "배", "대추", "감귤");
Stream<String> myStream = myList.stream();
- 중간 연산과 최종 연산으로 구분: 스트림 연산은 중간 연산(intermediate operation)과 최종 연산(terminal operation)으로 구분된다. 중간 연산은 스트림을 반환하여 연속적인 연산을 가능하게 하고, 최종 연산은 실제 결과를 반환하거나, 처리된 데이터를 소비한다.
List<String> myList = Arrays.asList("사과", "배", "대추", "감귤");
// 중간 연산: 필터링 - "감"으로 시작하는 요소만 필터링
Stream<String> filteredStream = myList.stream().filter(s -> s.startsWith("감"));
// 최종 연산: 각 요소를 출력
filteredStream.forEach(System.out::println);
- 지연 평가(lazy evaluation): 스트림의 중간 연산은 최종 연산이 호출되기 전까지 실행되지 않는다. 최종 연산이 호출될 때 비로소 필요한 연산이 수행되며, 이를 통해 연산의 효율성을 극대화할 수 있다.
2.2. 스트림의 병렬 처리
스트림은 parallel() 메서드를 통해 쉽게 병렬 처리를 수행할 수 있다. 병렬 스트림은 여러 스레드를 사용하여 동시에 데이터 요소를 처리하므로, 대용량 데이터 처리에 유리하다.
List<String> myList = Arrays.asList("사과", "배", "대추", "감귤");
// 병렬 스트림 생성 및 요소 출력
myList.parallelStream().forEach(System.out::println);
병렬 스트림을 사용할 때는 작업의 순서가 보장되지 않으므로, 순서가 중요한 연산에서는 주의해야 한다.
2.3. 스트림의 다양한 연산 예제
스트림을 이용하여 데이터를 필터링, 매핑, 정렬하는 다양한 예제를 살펴보자.
List<String> fruits = Arrays.asList("사과", "배", "대추", "감귤", "자몽");
// 중간 연산: "자"로 시작하는 요소만 필터링
// 중간 연산: 대문자로 변환
// 최종 연산: 각 요소 출력
fruits.stream()
.filter(fruit -> fruit.startsWith("자"))
.map(String::toUpperCase)
.forEach(System.out::println); // 출력: "자몽"
위의 예제에서는 "자"로 시작하는 과일 이름만 필터링하고, 필터링된 과일을 대문자로 변환한 후 출력한다.
2.4. 스트림의 수집 및 변환
스트림의 최종 연산 중 collect() 메서드를 사용하면 스트림의 데이터를 특정 컬렉션으로 변환하거나, 통계적인 값을 계산할 수 있다. 예를 들어, 필터링된 데이터를 List로 수집하려면 다음과 같이 작성할 수 있다.
List<String> fruits = Arrays.asList("사과", "배", "대추", "감귤", "자몽");
// 중간 연산: "사"로 시작하는 과일 이름만 필터링
List<String> filteredFruits = fruits.stream()
.filter(fruit -> fruit.startsWith("사"))
.collect(Collectors.toList());
System.out.println(filteredFruits); // 출력: [사과]
Collectors 유틸리티 클래스에는 다양한 수집기 메서드가 제공되며, toList(), toSet(), toMap()과 같이 컬렉션으로 변환할 수 있다.
3. 결론
람다 표현식과 스트림 API는 자바 8에서 도입된 강력한 기능으로, 개발자가 코드를 간결하고 명확하게 작성할 수 있도록 도와준다. 람다 표현식은 익명 함수 작성과 함수형 인터페이스 구현을 단순화하며, 스트림 API는 컬렉션 데이터를 효과적으로 처리하고 가공할 수 있도록 지원한다.
이 글에서는 람다 표현식과 스트림의 기본적인 개념과 예제를 소개하였으며, 이를 통해 자바 코드의 생산성을 높이는 방법을 설명하였다. 이 두 기능을 잘 활용하면 복잡한 코드 작성 및 데이터 처리 작업을 간단하고 효율적으로 구현할 수 있다.
'JAVA > JAVA 기초' 카테고리의 다른 글
Java 이너 클래스 (Inner Class) (0) | 2024.10.14 |
---|---|
JAVA I/O 입출력 시스템 (0) | 2024.10.13 |
Java Thread 활용 (0) | 2024.10.11 |
Java 예외(Exception) 처리 (0) | 2024.10.10 |
Java Collection(List, Set, Map, Queue) Framework (0) | 2024.10.08 |