☕JAVA/🐣 강의 [JAVA]

[JAVA][기초] Stream 스트림

디카페인라떼 2022. 11. 15. 19:49

[기초] 시리즈는 본강의를 수강후 정리한 글들 입니다.

Java for Beginner - YouTube

 

Java for Beginner

[교재 link] https://github.com/namoosori/java-for-beginner/tree/master/doc Java for Beginner 강의에서 다루는 내용은 다음과 같습니다. - Java 개요 : Java 언어가 어떤 언어이고 Java 언어를 이용해 프...

www.youtube.com


💡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 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()와 같은 연산이 있음.