TIL_230307_자바의 정석 복습_람다와 스트림 2

창고·2023년 3월 7일
0

Chapter 14. 람다와 스트림

2. 스트림(stream)

(1) 스트림이란?

  • 스트림이란?
    • 데이터 소스를 추상화하고 데이터를 다루는데 자주 사용되는 메서드들을 정의
    • 데이터 소스를 추상화 = 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게 되었다는 것과 코드의 재사용성이 높아진다는 것을 의미
    • 컬렉션, 배열에 데이터를 담고 원하는 결과를 얻기 위해 for문과 Iterator를 사용하던 과거 방식을 탈피 (또한 데이터 소스마다 다른 방식으로 다뤄야 하는 문제도 있었음)
// 과거 방식
String[] strArr = ["aaa", "ddd", "ccc"];
List<String> strList = Arrays.asList(strArr);
Collections.sort(strList)

for (String str : strList) {
	System.out.println(str)
}

// 스트림 사용
Stream<string> strStream1 = strList.stream(); // 스트림 생성
strStream1.sorted().forEach(System.out::println);
  • 스트림은 데이터 소스를 변경하지 않음
    • 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐, 데이터 소스를 변경하지 않음. 필요하다면 정렬된 결과를 컬렉션이나 배열에 담아 반환할 수도 있음
// 정렬된 결과를 새 리스트로 반환
List<String> sortedList = strStream1.sorted().collect(Collectors.toList());
  • 스트림은 일회용임
    • Iterator처럼 일회용이며 스트림은 한번 사용하면 닫혀서 다시 사용할 수 없음. 필요하다면 스트림을 다시 생성해야 함
strStream.sorted().forEach(System.out::println);
int numOfStr = strStream.count(); // 에러, 스트림이 닫혀서 다시 생성해야 함
  • 스트림은 작업을 내부 반복으로 처리
    • 내부 반복 : 반복문을 메서드의 내부에 숨길 수 있음을 의미
    • forEach()와 같은 메서드들은 매개변수에 대입된 람다식을 데이터 소스의 모든 요소에 적용
  • 스트림의 연산
    • 다양한 연산을 이용해 복잡한 작업들을 간단히 처리할 수 있음. 마치 DB에 SELECT문으로 질의(쿼리)하는 것과 같은 느낌
    • 중간 연산 : 연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산 가능
    • 최종 연산 : 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능
stream.distinct().limit(5).sorted().forEach(System.out::println);
// distinct, limit, sorted는 중간 연산
// forEach는 최종 연산
  • 지연된 연산
    • 스트림 연산에서 중요한 점은 최종 연산이 수행되기 전까지는 중간 연산이 수행되지 않는다는 것
    • 중간 연산을 호출하는 것은 어디까지나 어떤 작업이 수행되어야 하는지를 지정해주는 것
    • 최종 연산이 수행되어야 비로소 스트림의 요소들이 중간 연산을 거쳐 최종 연산에서 소모
  • 병렬 스트림
    • 스트림의 장점 중 하나가 병렬 처리가 쉽다는 것.
    • 병렬 스트림은 내부적으로 fork&join 프레임워크로 자동적으로 연산을 병렬로 수행
    • 스트림에 parallel() 이라는 메서드를 호출하면 병렬로 연산을 수행하도록 할 수 있음
      • parallel(), sequential()은 새로운 스트림을 생성하는 것이 아닌, 스트림의 속성을 변경
int sum = strStream.parallel() // 병렬 스트림으로 전환
				   .mapToInt(s -> s.length())
                   .sum();

(2) 스트림 만들기

  • 컬렉션
    • 컬렉션의 최고 조상인 Collection에 stream()이 정의되어 있음
    • Collection의 자손인 List와 Set을 구현한 컬렉션 크래스들은 모두 이 메서드로 스트림을 생성할 수 있음
Stream<T> Collection.stream()
  • 배열
    • 배열을 소스로 하는 스트림을 생성하는 메서드는 Stream과 Arrays에 static 메서드로 정의되어 있음
Stream<T> Stream.of(T... values) // 가변 인자
Stream<T> Stream.of(T[])
Stream<T> Arrays.stream(T[])
  • 특정 범위의 정수
    • IntStream과 LongStream은 지정된 범위의 연속된 정수를 스트림으로 생성해서 반환하는 range()와 rangeClosed()를 가지고 있음
IntStream IntStream.range(int begin, int end) // 경계의 끝인 end 미포함
IntStream IntStream.rangeClosed(int begin, int end) // 경계의 끝인 end 포함
  • 임의의 수
    • 난수를 생성하는데 사용하는 Random 클래스에는 아래와 같은 인스턴스 메서드들이 포함되어 있는데 해당 타입의 난수들로 이루어진 스트림을 반환
// 아래의 메서드를 통해 반환되는 스트림들은 크기가 정해지지 않은 무한 스트림으로 
// limit()도 같이 사용해서 크기를 제한해야 함
IntStream ints()
LongStream longs()
DoubleStream doubles()
  • 람다식 - iterate(), generate()
    • Stream 클래스의 iterate()와 generate()는 람다식을 매개변수로 받아서 람다식에 의해 계산되는 값들을 요소로 하는 무한 스트림을 생성
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
static <T> Stream<T> generate(Supplier<T> s)
  • 파일
    • java.nio.file.Files는 파일을 다루는데 필요한 유용한 메서드들을 제공하며 list()는 지정된 디렉토리(dir)에 있는 파일의 목록을 소스로 하는 스트림을 생성해서 반환
Stream<Path> Files.list(Path dir)
  • 빈 스트림
    • 요소가 하나도 없는 비어 있는 스트림을 생성하 ㄹ수 있으며 스트림에 연산을 수행한 결과가 없을 경우 null 보다는 빈 스트림을 반환하는 것이 좋음
    • count()는 스트림 요소의 개수를 반환
Stream emptyStream = Stream.empty(); // 빈 스트림 생성해서 반환
long count = emptyStream.count(); // count의 값은 0
  • 두 스트림의 연결
    • Stream의 static 메서드인 concat()을 사용하면 두 스트림을 하나로 연결할 수 있음
    • 물론 연결하려는 두 스트림의 요소는 같은 타입이어야 함
Stream<String> strs1 = Stream.of(str1);
Stream<String> strs2 = Stream.of(str2);

Stream<String> strs3 = Stream.concat(strs1, strs2); // 두 스트림을 하나로 연결

(3) 스트림의 중간 연산

  • 스트림 자르기 - skip(), limit()
    • skip()과 limit()는 스트림의 일부를 잘라낼 때 사용
    • skip()은 처음부터 매개변수로 받는 숫자 개의 요소를 건너뜀
    • limit()은 스트림의 요소를 매개변수로 받는 숫자 만큼으로 제한함
Stream<T> skip(long n) // 처음부터 n개까지의 요소를 건너 뜀
Stream<T> limit(long maxSize) // 스트림의 요소를 n개로 제한
  • 스트림의 요소 걸러내기 - filter(), distinct()
    • distinct()는 스트림에서 중복된 요소들을 제거
    • filter()는 주어진 조건(Predicate)에 맞지 않는 요소를 걸러냄
    • filter 내부에는 연산결과가 boolean인 람다식을 사용해도 됨
    • 필요하다면 조건을 달리 하여 filter를 여러 개 사용할 수 있음
Stream<T> filter(Predicate<? super T> predicate)
Stream<T> distinct()

// 두 문장은 동일한 효과
intStream.filter(i -> i%2 !=0 && i%3 != 0).forEach(...)
intStream.filter(i -> i%2 !=0).filter(i -> i%3 !=0).forEach(...)
  • 정렬 - sorted()
    • 스트림을 정렬할 때 사용
    • 자주 사용하는 메서드는 Comparator.comparing()
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)

// 예시
studentStream.sorted(Comparator.comparing(Student::getTotalScore)
							   .thenComparing(...))
  • 변환 - map()
    • 스트림의 요소에 저장된 값 중 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때 사용
Stream<R> map(Function<? super T, ? extends R> mapper)

// 예시
fileStream.map(File::getName)
	      .filter(...)
  • 조회 - peek()
    • 연산과 연산 사이에 올바르게 처리되어 있는지 확인할 때 사용
    • forEach()와 달리 스트림의 요소를 소모하지 않으므로 연산 사이에 여러 번 끼워 넣어도 문제가 되지 않음
fileStream.map(File::getName)
		  .filter(...)
          .peek(s -> System.out.println(...))
          .filter(...)
          .peek(...)
  • mapToInt(), mapToLong(), mapToDouble()
    • Stream<T> 타입의 스트림을 기본형 스트림으로 변환할 때 사용
  • flatMap() - Stream<T[]>Stream<T>로 변환
    • 스트림의 요소가 배열이거나 map()의 연산결과가 배열일 경우, 스트림의 타입이 Stream<T[]>일 경우 Stream<T>로 변환하는 것이 편하며 이 때 사용

(4) Optional<T>와 Optionalnt

  • Optional<T>
    • 제네릭 클래스로 T 타입의 객체를 감싸는 래퍼 클래스.
    • 모든 타입의 참조 변수를 담을 수 있음
    • 최종 연산이 Optional인 경우가 있는데 이는 최종 연산의 결과를 Optional 객체 담아서 반환하는 것
    • 변환된 결과가 null인지 매번 if문으로 체크를 하지 않아도 되고 Optional에 정의된 메서드를 통해 간단히 처리가 가능
  • Optional 객체 생성하기
    • of() 또는 ofNullable()을 사용 (참조변수가 null일 가능성이 있으면 ofNullable 사용)
String str = "abc";
Optional<String> optVal = Optional.of(str);
  • Optional 객체의 값 가져오기
    • get()을 사용하며 값이 null일 때를 대비해 orElse()로 대체할 값을 지정할 수 있음
    • orElse()의 변형으로 null을 대체할 값을 반환하는 람다식을 지정할 수 있는 orElseGet()과 null일 때 지정된 예외를 발생시키는 orElseThrow()가 있음
Optional<String> optVal = Optional.of(str);
String str1 = optVal.get();
String str2 = optVal.orElseThrow(NullPointerException::new);
  • OptionalInt, OptionalLong, OptionalDouble
    • IntStream과 같은 기본형 스트림에는 Optional도 기본형을 값으로 하는 OptionalInt, OptionalLong, OptionalDouble을 반환
profile
공부했던 내용들을 모아둔 창고입니다.

0개의 댓글