자바 8 버전부터 '람다식(Lambda Expression)이 지원되었다.
이때부터 자바는 기존의 객체지향 언어의 특징와 함수형 언어의 특징을 함께 가지게 되어, 간단한 작업에서 코드의 가독성과 효율성을 크게 향상 시켜주었다.
(매개변수) -> {실행문};
이렇게 표현되고, 접근자와 반환형이 모두 생략이 된다.
한 줄로 함수가 표현이 되는 것
// 람다식 X
void print() {
System.out.println("HELLO JAVA");
}
// 람다식 O
() -> System.out.println("HELLO JAVA");
위 두개의 메소드가 동일한 출력값을 가져온다.
// 람다식 X
void sum(int x, int y) {
return x+ y;
}
// 람다식 O
(x, y) -> x + y
이렇게 매개변수가 있는 경우에는 예상이 가능하다면 생략할 수 있다.
return문과 세미콜론도 생략이 가능하다.
하지만 쓰는게 더 가독성 면에서 좋아보임!
익명 클래스가 뭐냐 하면,,,
객체의 선언과 생성을 동시에 하면서 오직 하나의 객체를 생성하고 한번만 사용되고 마는 일회용 클래스이다.
람다식은 일회용으로 이름도 안 붙여지고 사용하고 끝이니 익명 클래스가 맞다.
하지만 기존의 익명 클래스는 람다식처럼 간결한 상황, 간단한 로직에서 사용되지는 않는다.
더 복잡한 로직과 메소드가 많은 경우에 익명 클래스가 적합하다.
람다식은 익명 클래스를 대체할 수는 있지만, 모든 익명 클래스를 사용하는 상황에서는 적합하지 않다!
람다와 같이 자바 8 버전에서 도입된 기능
배열과 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있게 해주는 반복자이다.
기존의 Iterator와 기능이 비슷하지만, 람다식으로 처리한다는 점에서 코드가 간결해지고 병렬 처리도 가능하다.
Iterator와 같이 한 번 사용한 스트림은 재사용이 불가능하다.
스트림은 다양한 연산은 제공하는데, 이를 사용해서 복잡한 작업들을 간단하게 처리할 수 있다.
이 연산들을 중간 연산과 최종 연산으로 분류할 수 있는데
스트림 연산에서 중요한 점은 최종 연산이 수행되기 전까지 중간 연산이 수행되지 않는다.
스트림에 대해 filter, map을 해도 바로 연산이 수행되지 않는다는 것
이 수행되지 않는 중간 연산이 지연된 연산이 된다.
// 컬렉션에서 생성
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream = list.stream();
// 배열에서 생성
String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array);
이런 식으로 컬렉션과 배열을 만들어 준 뒤, 스트림을 만든다.
filter
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 이름이 'A'로 시작하는 요소만 필터링
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println); // 출력: Alice
map
List<String> names = Arrays.asList("alice", "bob", "charlie");
// 모든 이름을 대문자로 변환
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println); // 출력: ALICE, BOB, CHARLIE
sorted
List<Integer> numbers = Arrays.asList(5, 3, 8, 1);
// 정렬된 요소 출력
numbers.stream()
.sorted()
.forEach(System.out::println); // 출력: 1, 3, 5, 8
distinct
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
// 중복 제거 후 출력
numbers.stream()
.distinct()
.forEach(System.out::println); // 출력: 1, 2, 3, 4, 5
forEach
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 각 요소 출력
names.stream()
.forEach(System.out::println); // 출력: Alice, Bob, Charlie
collect
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 이름을 대문자로 변환한 후 리스트로 수집
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperCaseNames); // 출력: [ALICE, BOB, CHARLIE]
reduce
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
// 모든 숫자의 합 계산
int sum = numbers.stream()
.reduce(0, Integer::sum);
System.out.println(sum); // 출력: 10
count
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 이름의 개수 계산
long count = names.stream()
.count();
System.out.println(count); // 출력: 3
스트림 작업을 여러 개의 CPU 코어에서 병렬 처리가 가능하다.
이를 통해서 대량의 데이터 처리 시 성능을 향상시킬 수 있다.
public class Main {
public static void main(String[] args) {
// 큰 데이터 세트
int max = 100_000_000;
// 순차 스트림
long start = System.currentTimeMillis();
long sumSequential = IntStream.rangeClosed(1, max)
.sum();
long end = System.currentTimeMillis();
System.out.println("Sequential Stream Time: " + (end - start) + " ms");
// 병렬 스트림
start = System.currentTimeMillis();
long sumParallel = IntStream.rangeClosed(1, max)
.parallel()
.sum();
end = System.currentTimeMillis();
System.out.println("Parallel Stream Time: " + (end - start) + " ms");
}
}
> Output
Sequential Stream Time: 34 ms
Parallel Stream Time: 14 ms
순차 스트림과 병렬 스트림의 실행 속도에서 차이가 나는 것을 확인할 수 있다.