남궁성님의 Java의 정석(3rd Edition)을 보고 정리한 글입니다.
1. 스트림(Stream)이란?
- Java 8에서 추가되었다.
- 컬렉션, 배열 등에 저장된 데이터를 for문을 사용하지 않고, 요소를 하나씩 참조해서 람다식과 함께 처리할 수 있도록 해주는 반복자이다.
스트림을 사용하기 전
public class Test {
public static void main(String[] args) {
String[] str = {"aaa", "ddd", "ccc"};
List<String> stringList = Arrays.asList(str);
Arrays.sort(str);
Collections.sort(stringList);
for(String s : str) {
System.out.println(s);
}
for(String s : stringList) {
System.out.println(s);
}
}
}
스트림 적용 코드
- stream() 메서드로 스트림을 생성할 수 있다.
- 스트림을 활용하면 재사용도 높아지고 간결하고 이해하기 쉽다.
public class Test {
public static void main(String[] args) {
String[] str = {"aaa", "ddd", "ccc"};
List<String> stringList = Arrays.asList(str);
Stream<String> stream1 = Arrays.stream(str); // 스트림 생성
Stream<String> stream2 = stringList.stream(); // 스트림 생성
stream1.sorted().forEach(System.out::println);
stream2.sorted().forEach(System.out::println);
}
}
2. 스트림 특징
스트림은 데이터 소스를 변경하지 않는다.(read only)
List<String> list = stream1.sorted().collect(Collectors.toList());
스트림은 일회용이다.
stream1.sorted().forEach(System.out::println);
int num = stream1.count(); // error.
스트림은 작업을 내부 반복으로 처리한다. (반복문을 메서드 내부에 숨긴다.)
stream.forEach(System.out:println);
// forEach 내부 구조
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
병렬로 처리(멀티 쓰레드)하기가 쉽다
- parallel()를 호출하면 병렬로 처리할 수 있다.
- sequential()을 호출하면 병렬로 처리하지 않는다.
int sum = strStream.parallel()
.mapToInt(s -> s.length))
.sum();
기본형 스트림(IntStream, LongStream, DoubleStream)을 제공한다.
- Stream<T>보다 기본형 스트림이 많은 숫자와 관련된 유용한 메서드를 제공을 제공
3. 스트림 연산
지연 연산 : 스트림은 최종 연산이 수행되기 전까지 중간 연산을 수행하지 않고, 최종 연산이 수행되면 스트림의 요소들이 중간 연산을 거쳐 최종 연산에 소모된다.
a. 중간연산
연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산을 할 수 있음.
distinct() : 중복 제거
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Distinct Numbers: " + distinctNumbers);\\
실행결과
Distinct Numbers: [1, 2, 3, 4, 5]
filter() : 조건에 안 맞는 요소 제외
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Even Numbers: " + evenNumbers);
실행결과
Even Numbers: [2, 4, 6, 8, 10]
limit() : 스트림 일부를 잘라낸다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> firstThree = numbers.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println("First Three Numbers: " + firstThree);
실행결과
First Three Numbers: [1, 2, 3]
skip() : 스트림일부를 건너뛴다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> afterSkipping = numbers.stream()
.skip(5)
.collect(Collectors.toList());
System.out.println("After Skipping Five Numbers: " + afterSkipping);
실행결과
After Skipping Five Numbers: [6, 7, 8, 9, 10]
peek() : 스트림의 요소에 작업 수행
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.peek(name -> System.out.println("Processing: " + name))
.collect(Collectors.toList());
실행결과
Processing: Alice
Processing: Bob
Processing: Charlie
sorted() : 스트림의 요소를 정렬한다.
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Sorted Numbers: " + sortedNumbers);
실행결과
Sorted Numbers: [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
map()
- 각 요소를 변환하여 새로운 요소를 생성
- 각 요소는 1:1로 매핑된다.
- mapToDouble(), mapToInt(), mapToLong(), mapToLong()도 지원한다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("Name Lengths: " + nameLengths);
실행결과
Name Lengths: [5, 3, 7]
flatMap()
- 각 요소를 다른 스트림으로 변환하고 이러한 스트림을 평탄화하여 하나의 스트림으로 만든다.
- 1:N, 또는 0:N으로 매핑되어 주로 중첩 리스트에 사용.
- flatMapToDouble(), flatMapToInt(), flatMapToLong()도 지원한다.
List<List<Integer>> numbers = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8)
);
List<Integer> flatList = numbers.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("Flat List: " + flatList);
실행결과
Flat List: [1, 2, 3, 4, 5, 6, 7, 8]
b. 최종연산
연산결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능.
forEach()
- 각 요소에 지정된 작업 수행
- 순서를 보장하지 않는다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEach(name -> System.out.println(name));
실행결과
Alice
Bob
Charlie
forEachOrdered()
- 각 요소에 지정된 작업 수행
- 순서를 보장한다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEachOrdered(name -> System.out.println(name));
실행결과
Alice
Bob
Charlie
count() : 스트림 요소 개수 반환
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.stream().count();
System.out.println("Number of elements: " + count);
실행결과
Number of elements: 5
max() : 최대값 반환
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5);
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
System.out.println("Max value: " + max.orElse(0));
실행결과
Max value: 5
min() : 최소값 반환
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5);
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
System.out.println("Min value: " + min.orElse(0));
Min value: 1
findAny() : 특정조건에 만족하는 요소 아무거나 하나 반환
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> anyName = names.stream().findAny();
System.out.println("Any name: " + anyName.orElse("No names found"));
실행결과
Any name: Alice
findFirst() : 특정조건에 일치하는 요소중 가장 앞에 있는 요소 반환
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> firstName = names.stream().findFirst();
System.out.println("First name: " + firstName.orElse("No names found"));
실행결과
First name: Alice
allMatch() : 주어진 조건을 모든 요소가 만족하면 true
List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0);
System.out.println("Are all numbers even? " + allEven);
실행결과
Are all numbers even? true
anyMatch() : 주어진 조건을 모든 요소 중 하나라도 만족하면 true
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
boolean hasCharlie = names.stream().anyMatch(name -> name.equals("Charlie"));
System.out.println("Does the list contain Charlie? " + hasCharlie);
실행결과
Does the list contain Charlie? true
noneMatch() : 주어진 조건을 모든 요소가 만족하지 않으면 true
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 9);
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);
System.out.println("Are all numbers non-negative? " + noneNegative);
실행결과
Are all numbers non-negative? true
toArray() : 스트림의 모든 요소를 배열로 반환
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String[] nameArray = names.stream().toArray(String[]::new);
System.out.println(Arrays.toString(nameArray));
실행결과
[Alice, Bob, Charlie]
reduce() : 스트림의 요소를 하나씩 줄여가면서 계산
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b);
System.out.println("Sum of numbers: " + sum.orElse(0));
실행결과
Sum of numbers: 15
collect() : 스트림의 요소를 수집한다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String concatenatedNames = names.stream().collect(Collectors.joining(", "));
System.out.println("Concatenated names: " + concatenatedNames);
실행결과
Concatenated names: Alice, Bob, Charlie
4. Optional이란?
- Java8에서 등장.
- Optional<T>는 T타입의 객체를 감싸는 Wrapper 클래스이다.
- 참조하더라도 NPE가 발생하지 않도록 도와준다.
- Stream과 마찬가지로 OptionalInt, OptionalDouble, OptionalLong 기본형 타입의 Optional을 제공한다.
public final class Optional<T> {
private final T value; // T 타입의 참조변수
// ...
}
Optional 사용하지 않은 코드
- strList가 null일 경우 NPE가 발생하기 때문에 이를 방지하고자 if문으로 조건 검사를 해야 한다.
- Optional은 NPE를 방지할 수 있는 다양한 메서드를 제공한다.
List<String> strList = getList();
if(strList != null) {
Collections.sort(strList);
}
a. Optional 사용법
Optional 객체 생성
Optional<String> optionalStr1 = Optional.of("abc");
Optional<String> optionalStr2 = Optional.of(null); // NPE 발생
Optional<String> optionalStr3 = Optional.ofNullable("abc");
Optional<String> optionalStr4 = Optional.ofNullable(null); // OK
Optional<String> optionalStr5 = null; // null로 초기화
Optional<String> optionalStr6 = Optional.<String>empty(); // 빈 객체로 초기화
Optional 객체 값 조회
Optional<String> optionalStr1 = Optional.of("abc");
// null이 아니면 값 반환, null이면 예외 발생
String s1 = optionalStr1.get();
// null이 아니면 값 봔환, null이면 "a" 반환
String s2 = optionalStr1.orElse("a");
// null이 아니면 값 반환, null이면 람다식 결과 반환
String s3 = optionalStr1.orElseGet(String::new);
// null이 아니면 값 반환, null이면 지정 예외 발생
String s4 = optionalStr1.orElseThrow(NullPointerException::new);
스트림 + Optional
Optional<String> optionalStr = Optional.ofNullable("123");
int result = optionalStr
.filter(x->x.length() > 0)
.map(Integer::parseInt).orElse(-1); // null 아니면 변환값 반환, null 이면 -1 반환
System.out.println(result); // 123
isPresent(), ifPresent(Consumer<T> block)
- isPresent: null이면 false, null이 아니면 true
- ifPresent(Consumer<T> block): ****null이면 아무 동작x, null이 아니면 람다식 실행
Optional<String> optionalStr = Optional.ofNullable("123");
// isPresent()
if(optionalStr.isPresent()) {
System.out.println(optionalStr.get()); // 123
}
// ifPresent(Consumer<T> block)
optionalStr.ifPresent(System.out::println); // 123
'Programming > Java' 카테고리의 다른 글
[Java] ProcessBuilder로 시스템 명령어 수행하기 (0) | 2024.10.08 |
---|---|
[Java] 클래스 생성과 인스턴스 생성 (1) | 2024.01.05 |
[Java] 쓰레드(Thread) - 6(쓰레드 동기화) (0) | 2023.12.10 |
[Java] 쓰레드(Thread) - 5(쓰레드 상태와 실행제어) (0) | 2023.12.10 |
[Java] 쓰레드(Thread) - 4(데몬 쓰레드) (0) | 2023.12.10 |