람다식 - iterate( ) & generate( )

iterate( ) & generate( )

  • 람다식을 매개변수로 받는다.
  • 람다식에 의해 계산되는 값들을 요소로 하는 무한 스트림을 생성한다.
// iterate와 generate
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
static <T> Stream<T> generate(Supplier<T> s)

iterate( )

  • 씨앗값(seed)으로 지정된 값부터 시작해서, 람다식으로 계산된 결과를 다시 seed값으로 해서 계산을 반복한다.
// 0부터 시작해서 값이 2씩 계속 증가하는 예제코드
Stream<Integer> evenStream = Stream.iterate(0, n -> n+2); // 0,2,4,6, ...

generate( )

  • 람다식에 의해 계산되는 값을 요소로 하는 무한 스트림을 생성해서 반환한다.
  • iterate( )와는 달리, 이전 결과를 이용해서 다음 요소를 계산하지 않는다.
  • 매개변수가 Supplier<T>이므로, 매개변수가 없는 람다식만 허용된다.
// generate() 사용 예제 코드
Stream<Double> randomStream = Stream.generate(Math::random);
Stream<Integer> oneStream = Stream.getnerate( () -> 1);

💡 iterate( )와 generate( )에 의해 생성된 스트림을 기본형 스트림 타입의 참조변수로 다룰 수 없다는 것에 주의해야 한다.

// iterate()와 generate()를 기본형 스트림 타입으로 다루는 예제(에러 케이스)
IntStream     evenStream = 		Stream.iterate(0, n -> n+2); 	// 에러
DoubleStream  randomStream = 	Stream.generate(Math::random); 	// 에러

기본형 스트림 타입의 참조변수로 꼭 다뤄야 하는 경우라면 mapToInt( )와 같은 메서드로 변환을 해야 한다.

IntStream evenStream = Stream.iterate(0, n -> n+2).mapToInt(Integer::valueOf);
Stream<Integer> stream = evenStream.boxed(); // IntStream -> Stream<Integer>

빈 스트림

  • 요소가 하나도 없는 비어있는 스트림을 생성할 수도 있다.
  • 스트림에 연산을 수행한 결과가 하나도 없을 때, null보다 빈 스트림을 반환하는 것이 낫다.
// 빈 스트림을 반환하는 예제
Stream emptyStream = Stream.empty(); // empty()는 빈 스트림을 생성해서 반환한다.
long count = emptyStream.count();	// count의 값은 0

두 스트림의 연결

  • Stream의 static 메서드인 concat( )을 사용하여 스트림을 연결할 수 있다.
  • 연결하려는 두 스트림의 요소는 같은 타입이어야 한다.
// Stream 연결 예제
String[] str1 = {"123", "456", "789"};
String[] str2 = {"ABC", "abc", "DEF"};

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

스트림의 중간연산

스트림 자르기 - skip( ), limit( )

  • skip( ) : 특정 요소를 건너뛸때 사용한다.
  • limit( ) : 스트림의 요소 개수를 제한할때 사용한다.
// skip()
Stream<T> skip(long n)

// limit()
Stream<T> limit(long maxsize)
// 10개의 요소를 가진 스트림에 skip()과 limit()을 적용하는 예제
IntStream intStream = IntStream.rangeClosed(1, 10); 	// 1~10의 요소를 가진 스트림

intStream.skip(3).limit(5).forEach(System.out::print); // 45678

스트림의 요소 걸러내기 - filter( ), distinct( )

  • distinct( ) : 스트림에서 중복된 요소들을 제거한다.
  • filter( ) : 주어진 조건(Predicate)에 맞지 않는 요소들을 걸러낸다.
// filter()
Stream<T> filter(Predicate<? super T> predicate)

// distinct()
Stream<T> distinct()

distinct( )

// distinct() 사용 예제
IntStream intStream = IntStream.of(1, 2, 2, 3, 3, 3, 4, 5, 5, 6);
intStream.distinct().forEach(System.out::print); // 123456

filter( )

  • filter( )는 매개변수로 Predicate를 필요로 한다.
  • Predicate대신 연산결과가 boolean 타입인 람다식을 사용해도 된다.
// 조건이 하나인 filter() 예제
IntStream intStream = IntStream.rangeClosed(1,10); // 1~10
intStream.filter(i -> i%2 == 0).forEach(System.out::println); // 246810

// 조건이 두개인 filter() 예제
intStream.filter(i -> i%2 != 0 && i%3 != 0).forEach(System.out::print); // 157
intStream.filter(i -> i%2 != 0).filter(i -> i%3 != 0).forEach(System.out::print);

💡 필요하면 filter( )를 다른 조건으로 여러 번 사용할 수도 있다.

정렬 - sorted( )

  • 스트림을 정렬할 때는 sorted( )를 사용한다.
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
  • sorted( )는 지정된 Comparator로 스트림을 정렬한다.
  • Comparator 대신 int값을 반환하는 람다식을 사용하는 것도 가능하다.
  • Comparator를 지정하지 않으면 스트림 요소의 기본정렬 기준으로 정렬된다.
  • 스트림의 요소가 Comparable을 구현한 클래스가 아니면 예외가 발생한다.

정렬에 사용되는 메서드

  • 정렬에 사용되는 메서드의 개수가 많지만, 기본적으로 comparing( )을 사용한다.
comparing(Function<T, U> keyExtractor)
comparing(Function<T, U) ketExractor, Comparator<U> keyComparator)
  • 비교대상이 기본형일 경우, 아래 메서드를 사용하면 오토박싱과 언박싱과정이 없어서 더 효율이 좋아진다.
// 기본형이 비교 대상일 경우 사용하면 좋은 메서드
comparingInt(ToIntFunction<T> keyExractor)
comparingLong(ToLongFunction<T> keyExractor)
comparingDouble(ToDoubleFunction<T> keyExractor)

// 정렬 조건을 추가하는 메서드
thenComparing(Comparator<T> other)
thenComparing(Function<T, U> keyExractor)
thenComparing(Functino<T, U> keyExractor, Comparator<U> keyComp)
// 학생 스트림을 반별, 성적순, 그리고 이름 순으로 정렬하여 출력하는 예제
studentStream.sorted(Comparator.comparing(Student::getBan)
			.thenComparing(Student::getTotalScore)
            .thenComparing(Student::getName)
            .forEach(System.out::println);

변환 - map( )

  • 스트림의 요소에 저장 된 값 중에서 원하는 필드만 뽑아내거나, 특정 형태로 변환해야 할때 사용한다.
  • map( ) 역시 중간 연산이다.
  • map( )도 filter( )처럼 하나의 스트림에 여러 번 적용할 수 있다.
// map() 메서드의 선언부
Stream<R> map(Function<? super T, ? extends R> mapper)

// File의 스트림에서 파일의 이름만 뽑아서 출력하는 예제
Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1"),
		new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt"));

// map()으로 Stream<File>을 Stream<String>으로 변환
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println); // 스트림의 모든 파일이름을 출력
// File의 스트림에서 파일의 확장자만을 뽑고, 중복을 제거해서 출력하는 예제
fileStream.map(File::getName)   				// Stream<File> -> Stream<String>
  .filter(s -> s.indexOf('.') != -1)  			// 확장자가 없는 것은 제외
  .map(s -> s.substring(s.indexOf('.') + 1)) 	// Stream<String> -> Stream<String>
  .distinct()									// 중복 제거
  .forEach(System.out::print);					// JAVABAKTXT

조회 - peek( )

  • 연산과 연산 사이에 올바르게 처리되었는지 확인할때 유용하다.
  • forEach( )와 다르게 스트림의 요소를 소모하지 않는다.
  • 연산 사이에 여러 번 끼워 넣어도 문제가 되지 않는다.
// peek() 사용예제
fileStream.map(File::getName) 							// Stream<File> -> Stream<String>
  .filter(s -> s.indexOf('.' != -1) 					// 확장자가 없는 것은 제외
  .peek(s -> System.out.printf("filename = %s%n" , s)) 	// 파일명을 출력한다.
  .map(s -> s.substring(s.indexOf('.') + 1)) 			// 확장자만 추출
  .peek(s -> System.out.printf("extension = %s%n" , s)) // 확장자를 출력한다.
  .forEach(System.out::println);

출처 : 자바의 정석 3rd Edition(남궁성 저, 도우출판)

0개의 댓글