[기초] 시리즈는 본강의를 수강후 정리한 글들 입니다.
💡Stream API의 이해와 활용
List<String> list = Arrays.asList("Lee","Park","Kim");
// 기존
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
// Stream 활용
list.stream().forEach(name->System.out.println(name));
- Java8에 추가된 Stream API를 활용하면 다양한 데이터 소스를 표준화된 방법으로 다룰 수 있음.
- 따라서 Collention F/W를 통해 관리하는 데이터를 처리하기 위해 주로 사용
- Stream API의 활용을 통해 수집된 다양한 데이터를 활용하는데 있어서 간결하고 가독성 있는 처리가 가능
- Stream API의 다양한 기능들은 대부분 람다를 필요로 하기 때문에 람다에 대한 이해가 선행되어야 함!
💡Stream Interface
- Stream API의 최상위 인터페이스는 BaseStream 인터페이스이지만 직접사용하는 경우는 없음.
- 주로 사용하는 인터페이스는 Stream 인터페이스이며 BaseStream을 상속하는 인터페이스
- Stream 인터페이스는 여러 메소드들을 정의하고 있으며 많은 메소드들이 파라미터에 람다와 메소드 참조가 필요
- Stream을 구현한 객체의 주요 특징은 불변성이며 Stream을 통해 얻은 결과는 새롭게 생성된 데이터임.
💡Stream 객체 생성
Collection 객체를 통한 방법 (주로 사용하는 방법)
List<String> list =Arrays.asList("Lee", "Kim", "Park", "Hong", "Choi", "Song");
Stream<String> stream = list.stream(); // Stream 생성
//stream.forEach( name -> System.out.println(name));
List<String> list =Arrays.asList("Lee", "Kim", "Park", "Hong", "Choi", "Song");
Stream<String> stream = list.stream(); // Stream 생성
System.out.println(stream.count()); // Stream 사용
stream.forEach(System.out::println); // Exception 발생 (stream has already been operated upon or closed)
- 처리할 데이터가 이미 존재하고 이를 처리하기 위한 일반적인 생성 방식
- Collection 인터페이스는 stream() 메소드를 default 메소드로 정의함.
- 이 메소드는 해당 컬렉션이 가지고 있는 항목들에 대해 스트림 처리가 가능한 Stream 객체를 반환
- 한번 생성한 스트림은 사용 후 다시 사용할 수 없으며 전체 데이터에 대한 처리가 이루어지면 종료
Stream 빌더를 통한 방법
Stream.Builder<String> builder = Stream.builder();
builder.accept("Kim");
builder.accept("Lee");
builder.accept("Song");
builder.accept("Park");
builder.accept("Lee");
Stream<String> stream = builder.build();
stream.forEach(System.out::println);
- 스트림 자체적으로 데이터를 생성하고 처리 가능
- Stream.Builder 인터페이스는 Consumer 인터페이스를 상속하고 있으며 데이터를 추가하는 accept(), add() 메소드와 데이터의 추가 작업을 완료하고 Stream을 반환하는 build() 메소드를 정의하고 있음.
💡Stream 연산
- Stream을 이용한 연산은 각 연산의 연결을 통해 파이프라인을 구성할 수 있음.
- 스트림 대상 데이터에 대한 다양한 연산을 조합할 수 있다는 것
- 스트림을 이용한 연산 처리는 스트림 객체의 생성부터 중간 연산, 그리고 최종 연산단계로 구분
- 스트림 객체가 제공하는 다양한 연산을 이해하고 연산에 필요한 람다표현식을 이해하고 적용하는 것이 중요
💡중간연산 intermediate operation
- filter, map과 같은 연산으로 Stream을 반환
- 중간 연산은 연속해서 호출하는 메소드 체이닝 Method Chaining 으로 구현
- 최종 연산이 실행되어야 중간연산이 처리되므로 중간 연산들로만 구성된 메소드 체인은 실행되지 않음.
- 최종 연산이 반드시 필요!
필터링 filtering
- 전체 데이터에서 불필요한 데이터를 없애고 원하는 데이터를 정확히 추출하기 위한 과정
- Stream API의 filter(), distinct()와 같은 메소드를 이용해 데이터 추출이나 중복제거를 구현
- 필터링 연산은 스트림의 중간 연산으로 필터링 결과를 Stream 객체로 반환하며 연산 완료를 위한 최종 연산이 필요
- 데이터의 중복을 제거하는 distinct()는 병렬 스트림의 경우 성능에 대한 고려가 필요하며 중복 객채의 비교는 equals() 메소드를 이용하기 때문에 이에 대한 고려 또한 필요
정렬 Sorted()
- Stream API의 sorted() 메소드는 특정 조건에 따라 데이터를 정렬하고 이를 다시 Stream으로 반환
- sorted()를 이용한 정렬을 위해서는 반드시 대상 객체들이 Comparable 인터페이스를 구현한 클래스, 즉 비교 가능한 객체여야 함.
- 어떤 기준으로 정렬을 할건지 비교 내용을 정해주어야 함.
- Comparable의 추상 메소드 compareTo 를 재정의 오버라이딩 하여 기준 정립 => 이렇게 해야 비교가능한 객체가 됨.
- 파라미터가 없는 정렬 과 파라미터로 Comparator를 이용해서 비교조건을 주어서 정렬하는 것 두가지가 있음.
- 기본 정렬은 파라미터 없어도 되나 (오름차순)
- Comparable 객체가 아닐 경우나 역순 정렬, 혹은 다른 조건의 정렬에는 Comparator 인터페이스가 제공하는 여러 default, static 메소드를 이용해 정렬을 구현할 경우 파라미터가 반드시 주어야 함!
매핑 Map()
- 스트림의 매핑 map 연산은 스트림이 관리하는 데이터를 다른 형태의 데이터로 변환할 수 있도록 함.
- 매핑 연산의 메소드는 map(), mapToInt(), mapToDouble(), mapToLong()이 있음
- 주로 사용하는 메소드는 map() 메소드이며 파라미터는 Function 함수형 인터페이스
- double, int, long 기본형 데이터 타입의 데이터를 처리하기 위한 메소드들은 매핑된 값의 결과가 기본형 데이터 타입일 경우 적용하여 사용
💡최종연산 terminal operation
연산 | 반환 형식 |
forEach() | 스트림의 각 요소를 소비하며 람다 적용. void 반환 |
count() | 스트림 요소의 수를 반환. Long 반환 |
collect() | List, Map 형태의 컬렉션 반환 |
sum() | 스트림의 모든 요소에 대한 합을 반환 |
reduce() | 스트림의 요소를 하나씩 줄여가며 연산 수행 후 결과 반환. Optional 반환 |
allMatch() | 파라미터로 전달되는 람다식 기준으로 스트림 데이터가 모두 일치하는지 확인 |
anyMatch() | 파라미터로 전달되는 람다식 기준으로 스트림 데이터가 하나라도 일치하는지 확인 |
noneMatch() | 파라미터로 전달되는 람다식 기준으로 스트림 데이터가 모두 일치하지 않는지 확인 |
findFirst() | 스트림데이터 중에서 가장 첫번째 데이터를 반환 |
- 최종연산은 forEach, collect와 같은 연산으로 void를 반환하거나 컬렉션 타입을 반환
- 중간 연산을 통해 가공된 스트림은 마지막으로 최종연산을 통해 각 요소를 소모하여 결과를 출력함
- 즉, 지연 lazy 되었던 모든 중간 연산들이 최종 연산시 모두 수행되는 것
- 최종 연산 후에는 한번 생성해서 소모한 스트림은 닫히게 되고 재사용이 불가능
- 스트림이 관리하는 전체 데이터에 대한 순회 작업은 최종 연산인 forEach() 메소드를 이용
- collect() 메소드는 스트림 처리 이후 처리된 데이터에 대해 Collection 객체로 반환하는 메소드
- 스트림의 최종 연산은 forEach()와 같은 스트림 처리 결과를 바로 확인할 수 있는 연산이 있고, 데이터를 모두 소모한 이후에 그 결과를 알 수 있는 count()와 같은 연산이 있음.
'☕JAVA > 🐣 강의 [JAVA]' 카테고리의 다른 글
[JAVA][기초] 람다식 / 함수형 인터페이스 (0) | 2022.11.15 |
---|---|
[JAVA][기초] 함수형 프로그래밍 / 중첩클래스 (0) | 2022.11.12 |
[JAVA][기초] Map 인터페이스 / Iterator 인터페이스 (0) | 2022.11.10 |
[JAVA][기초] Collection 인터페이스 - List / Set 인터페이스 (0) | 2022.11.10 |
[JAVA][기초] Java Collection Framework 와 Generic 제네릭의 이해 (0) | 2022.11.10 |