관리 메뉴

나구리의 개발공부기록

자바의 정석 기초편 ch14 - 23 ~ 34[스트림의 연산들, 스트림의 중간연산] 본문

유튜브 공부/JAVA의 정석 기초편(유튜브)

자바의 정석 기초편 ch14 - 23 ~ 34[스트림의 연산들, 스트림의 중간연산]

소소한나구리 2023. 12. 21. 12:10

1) 스트림의 연산들

(1) 스트림의 중간연산 요약

중간연산 설명
Stream<T> distinct() 중복을 제거
Stream<T> filter(Predicate<T> predicate) (조건식 입력) 조건에 안 맞은 요소를 제외
Stream<T> limit(long maxSize) 스트림의 일부를 잘라냄
Stream<T> skip(long n) 스트림의 일부를 건너뜀
Stream<T> peek (Consumer<T> action) (forEach 와 비슷) 스트림의 요소에 작업수행
Stream<T> sorted() (기본정렬)
Stream<T> sorted(Comparator<T> comparator) (정렬기준을 입력)
스트림의 요소를 정렬
Stream<R>
DoubleStream
Int Stream
LongStream
map(Function<T,R> mapper)
mapToDouble(ToDoubleFunction<T> mapper)
mapToInt(ToIntFunction<T> mapper)
mapToLong(ToLongFunction<T> mapper)

flatMap(Function<T,Stream<R>> mapper)
flatMapToDouble(Function<T, DoubleStream> m)
flatMapToInt(Function<T,IntStream> m)
flatMapToLong(Function<T, LongStream> m)
스트림의 요소를 변환
(중간연산의 핵심)

 

(2) 스트림의  최종연산 요약

최종연산 설명
void forEach(Consumer<? super T> action)
void forEachOrdered(Consumer<? super T> action) (순서유지(병렬스트림))
각 요소에 지정된 작업 수행
long count() 스트림의 요소의 개수 반환
Optional<T> max(Comparator<? super T> comparator) 
Optional<T> min(Comparator<? super T> comparator) 
스크림의 최대/최소값을 반환
Optional<T> findAny()  (아무거나 하나(병렬))
OPtional<T> findFirst() (첫 번째 요소(직렬 - 병렬이 아닐 때))
(filter()와 같이 쓰임, Optional<T> = 래퍼클래스)
스트림의 요소 하나를 반환
boolean allMatch(predicate<T> p)     (모두 만족하는지)
boolean anyMatch(predicate<T> p)   (하나라도 만족하는지)
boolean noneMatch(predicate<T> p) (모두 만족하지 않는지)
주어진 조건을 모든 요소가 만족시키는지,
만족하지 않는지 확인
Object[] toArray()
A[] toArray(IntFunction<A[]> generator)
스트림의 모든 요소를 배열로 반환
Optional<T> reduce(BinaryOperator<T> accumulater)
T reduce(T iidentity, BinaryOperator<T> accumulator)
U reduce(U identity, Bifunction<U,T,U> accumulator,
                                                                    binaryOperator<U> combiner)
스트림의 요소를 하나씩 줄여가면서(리듀싱) 계산
(최종연산의 핵심)
R collect(Collector<T,A,R> collector)
R collect(Supplier<R> supplier, Biconsumer<R,T> accumulater,
                                                       BiConsumer<R,R> combiner)
스트림의 요소를 수집
주로 요소를 그룹화, 분할한 결과를 컬렉션에 담아 반환하는데 사용
(최종연산의 핵심)

 

2) 스트림의 중간연산 설명

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

  • skip(): 앞에서부터 인수의 개수만큼 건너뜀
  • limit(): 인수의 길이까지만 반환
Stream<T> skip(long n)        // 앞에서부터 n개 건너뛰기
Stream<T> limit(long maxSize) // maxSize 이후의 요소는 잘라냄

IntStream skip(long n) 
IntStream limit(long maxSize) 

IntStream intStream = IntStream.rangeClosed(1, 10);    // 12345678910
intStream.skip(3).limit(5).forEach(System.out::print); // 45678

 

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

  • filter(): 인수로 람다식을 입력하면 해당 조건에 맞지 않은 요소를 스트림에서 제거함
  • distinct(): 스트림에 있는 요소 중 중복값을 제거
Stream<T> filter(Predicate<? super T> predicate) // 조건에 맞지 않는 요소 제거
Stream<T> distinct()                             // 중복제거

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

// 2의 배수만 출력	
IntStream intStream = IntStream.rangeClosed(1, 10);       // 12345678910
intStream.filter(i->i%2==0).forEach(System.out::print);   // 246810 

// 필터의 연속사용(2의 배수가 아니고 3의 배수가 아닌것 출력)
intStream.filter(i->i%2!=0 && i%3!=0).forEach(System.out::print);
intStream.filter(i->i%2!=0).filter(i->i%3!=0).forEach(System.out::print);

 

(3) 스트림 정렬하기 - sorted()

  • 다양한 정렬 기준을 사용할 수 있으며 대부분의 경우 메서드의 체인 메서드로.reversed()를 호출하면 역순정렬이 됨
  • comparing()메서드로 정렬기준을 입력할 수 있음
Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");

// 기본 정렬 사용
String result1 = strStream.sorted().collect(Collectors.joining());
System.out.println(result1); // CCaaaabccdd

// 기본 정렬 (명시적으로 Comparator.naturalOrder() 사용)
result1 = strStream.sorted(Comparator.naturalOrder()).collect(Collectors.joining());
System.out.println(result1); // CCaaaabccdd

// 람다식으로 정렬 기준을 입력
result1 = strStream.sorted((s1, s2) -> s1.compareTo(s2)).collect(Collectors.joining());
System.out.println(result1); // CCaaaabccdd

// 메서드 참조로 기본 정렬을 사용
result1 = strStream.sorted(String::compareTo).collect(Collectors.joining());
System.out.println(result1); // CCaaaabccdd

// Comparator의 reverseOrder()를 사용하여 역순 정렬
result1 = strStream.sorted(Comparator.reverseOrder()).collect(Collectors.joining());
System.out.println(result1); // ddccbaaaCС

// 위 문장과 동일함
result1 = strStream.sorted(Comparator.<String>naturalOrder().reversed()).collect(Collectors.joining());
System.out.println(result1); // ddccbaaaCС

/**
* String 클래스 내부에는 이미 Comparator를 만들어서 static 멤버로 가지고 있음
* 즉, String클래스의 정렬 기준도 호출하여 사용할 수 있음
*/
static Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();

// String클래스의 CASE_INSENSITIVE_ORDER 정렬기준을 사용 - 대소문자 구분한하는 기본정렬
result1 = strStream.sorted(String.CASE_INSENSITIVE_ORDER).collect(Collectors.joining());
System.out.println(result1); // aaabCCccdd

// 위 기능의 역순정렬 즉, 대소문자 구분안함(동일 순서일때는 대문자가 먼저 출력됨) + 역순
result1 = strStream.sorted(String.CASE_INSENSITIVE_ORDER.reversed()).collect(Collectors.joining());
System.out.println(result1); // ddCCccbaaa

// 길이 순 정렬 (메서드 참조로 인수의 길이를 입력)
result1 = strStream.sorted(Comparator.comparing(String::length)).collect(Collectors.joining());
System.out.println(result1); // bddCCccaaa

// 길이 순 정렬 (메서드 참조 대신 람다식 사용)
result1 = strStream.sorted(Comparator.comparingInt(s -> s.length())).collect(Collectors.joining());
System.out.println(result1); // bddCCccaaa

// 길이 순 정렬의 역순
result1 = strStream.sorted(Comparator.comparing(String::length).reversed()).collect(Collectors.joining());
System.out.println(result1); // aaaddCCccb

 

(4) 정렬 대상, 정렬 기준을 제공 - comparing(), thenComparing()

  • sorted()이 인수로 Comparator의 comparing()메서드를 사용하여 정렬 대상과 정렬기준을 입력할 수 있음
  • 추가 정렬 기준을 제공하고자 할 경우 .thenComparing()을 사용하여 인수에 정렬기준을 추가로 입력할 수 있음
Stream<T> sorted() // 스트림 요소의 기본 정렬(Comparable)로 정렬
Stream<T> sorted(Comparator<? super T> comparator) //지정된 Comparator로 정렬

comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)

// 정렬 기준이 3개, thenComparing으로 정렬기순을 추가로 제공
studentStream.sorted(Comparator.comparing(Student::getBan)  // 반별로 정렬
             .thenComparing(Student::getTotalScore)         // 총점별로 정렬
             .thenComparing(Student::getName)               // 이름별로 정렬
             .forEach(Sysetm.out::println);

 

(5) 예제1

  • 스트림의 요소 중 반을 기준으로 기본 정렬을 수행후 출력
import java.util.*;
import java.util.stream.*;

class Ex14_5 {
	public static void main(String[] args) {
		Stream<Student> studentStream = Stream.of(
						new Student("이자바", 3, 300),
						new Student("김자바", 1, 200),
						new Student("안자바", 2, 100),
						new Student("박자바", 2, 150),
						new Student("소자바", 1, 200),
						new Student("나자바", 3, 290),
						new Student("감자바", 3, 180)
					);

		// 역순 정렬을 하고 싶은 곳에는 .reverse()를 중간에 넣으면 됨
		studentStream.sorted(Comparator.comparing(Student::getBan) // 반별 정렬
	//	위 코드를 람다식으로 변경
	//	studentStream.sorted(Comparator.comparing((Student s) -> s.getBan())
		 	.thenComparing(Comparator.naturalOrder()))     	// 기본 정렬
			.forEach(System.out::println);
	}
}

class Student implements Comparable<Student> {
	String name;
	int ban;
	int totalScore;
	Student(String name, int ban, int totalScore) { 
		this.name =name;
		this.ban =ban;
		this.totalScore =totalScore;
	}

	public String toString() { 
	   return String.format("[%s, %d, %d]", name, ban, totalScore); 
	}

	String getName()     { return name;}
	int getBan()         { return ban;}
	int getTotalScore()  { return totalScore;}

   // 총점 내림차순을 기본 정렬로 한다.
	public int compareTo(Student s) { 
		return s.totalScore - this.totalScore;
	}
}	

/*
출력결과
[김자바, 1, 200]
[소자바, 1, 200]
[박자바, 2, 150]
[안자바, 2, 100]
[이자바, 3, 300]
[나자바, 3, 290]
[감자바, 3, 180]
*/

 

(6) 스트림의 요소를 변환 - map()

  • map()메서드로 Stream에 저장된 요소의 타입을 변환함
  • 인수로 매퍼로 동작할 코드를 입력

// 사용 예시
Stream<R> map(Function<? super T, ? extends R> mapper) // Stream<T> -> Stream<R> 변환

// 파일 스트림 생성
Stream<File> fileStream = Stream.of(
    new File("Ex1.java"), new File("Ex1"), new File("Ex1.bak"),
    new File("Ex2.java"), new File("Ex1.txt"));

// 파일명을 스트림으로 변환
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println); // 스트림의 모든 파일의 이름을 출력


// 파일 스트림에서 파일 확장자를 대문자로 중복을 제거하여 뽑아내기
fileStream = Stream.of(
    new File("Ex1.java"), new File("Ex1"),
    new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt")
);

// 파일 스트림에서 파일 확장자(대문자)를 중복없이 뽑아내기
fileStream.map(File::getName)              // Stream<File> -> Stream<String>
          .filter(s -> s.indexOf('.') != -1)  // 확장자가 없는 것은 제외
          .map(s -> s.substring(s.indexOf('.') + 1))  // Stream<String> -> Stream<String>
          .map(String::toUpperCase)        // Stream<String> -> Stream<String>
          .distinct()                      // 중복 제거
          .forEach(System.out::print);     // JAVABAKTXT
}

 

(7) peek()

  • 스트림의 요소를 소비하지 않고 확인할 수 있음
  • 중간 작업결과를 확인하는 용도 즉, 디버깅을 목적으로 사용함
Stream<T>  peek(Consumer<? super T> action)    // 중간 연산(스트림을 소비X)
void       forEach(Consumer<? super T> action) // 최종 연산(스트림을 소비O)

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);                   // 최종연산 스트림을 소비

 

(8) 예제

  • 파일명.확장자 -> 확장자만 추출(중복제거)
import java.io.*;
import java.util.stream.*;

class Ex14_6 {
	public static void main(String[] args) {
		File[] fileArr = { new File("Ex1.java"), new File("Ex1.bak"),
			new File("Ex2.java"), new File("Ex1"), new File("Ex1.txt")
		};

		Stream<File> fileStream = Stream.of(fileArr);

		// map()으로 Stream<File>을 Stream<String>으로 변환
//		Stream<String> filenameStream = fileStream.map(File::getName);
		Stream<String> filenameStream = fileStream.map((f) -> f.getName());// 람다식으로 변환
		filenameStream.forEach(System.out::println); // 모든 파일의 이름을 출력

		fileStream = Stream.of(fileArr);  // 스트림을 다시 생성

		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)) // 확장자만 추출
			  .map(String::toUpperCase)     // 모두 대문자로 변환
			  .distinct()                   //  중복 제거
			  .forEach(System.out::println);  // JAVABAKTXT	

		System.out.println();
	}
}

 

(9) 스트림의 스트림을 스트림(하나의 스트림)으로 변환 - flatMap()

  • 스트림 배열을 스트림의 스트림(2차원 스트림)이 아닌 하나의 스트림으로 변환하여 1차원 배열처럼 사용할 수 있도록 변환
  • 스트림의 스트림은 내부의 스트림의 요소에 직접 접근이 불가능한데 flatMap()으로 변환하면 각 스트림의 요소에 직접 접근할 수 있음
// 스트링 배열로 스트림을 생성하여 각 요소에 스트링배열을 생성
Stream<String[]> strArrStrm = Stream.of(new String[]{"abc", "def", "ghi"  },
                                        new String[]{"ABC", "GHI", "JKLMN"});
                                        
// 스트림배열을 스트림의스트림으로(2차원 배열구조) 전환 - map
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);

// 스트림배열을 스트림으로 전환 - flatMap
Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream); // Arrays.stream(T[])

스트림 배열을 map()으로 변환하면 2차원 스트림 배열이 됨
flatMap()메서드로 스트림배열을 1차원 스트림으로 변환

 

(10) 예제

  • 스트림의 스트림을 스트림으로 변환 후 요소 중복제거, 기본정렬, 소문자변환하여 출력
  • 스트링 배열의 요소를 스트림으로 변환 후 하나이상의 공백(정규식으로 표현)으로 분할하여 중복제거, 소문자변환, 기본정렬하여 출력
import java.util.*;
import java.util.stream.*;

class Ex14_7 {
	public static void main(String[] args) {
		Stream<String[]> strArrStrm = Stream.of(
			new String[]{"abc", "def", "jkl"},
			new String[]{"ABC", "GHI", "JKL"}
		);

// 스트림의 스트림으로 변경하면 직접 접근이 불가능
//		Stream<Stream<String>> strStrmStrm = strArrStrm.map(Arrays::stream);
		Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream); // 스트림으로 변환

		strStrm.map(String::toLowerCase)	// 스트림요소를 소문자로 모두 변환
			   .distinct()	// 중복제거
			   .sorted()	//정렬
			   .forEach(System.out::println);
		System.out.println();

		// 문장의 단어를 스트림의 요소로 변환
		String[] lineArr = {
			"Believe or not It is true",
			"Do or do not There is no try",
		};

		Stream<String> lineStream = Arrays.stream(lineArr);
        
		// " +" -> 하나이상의 공백(정규식 중 하나)
		// 문자열 -> 문자열 배열로 변환
		lineStream.flatMap(line -> Stream.of(line.split(" +")))
			.map(String::toLowerCase)
			.distinct()
			.sorted()
			.forEach(System.out::println);
		System.out.println();
	}
}

 

** 출처 : 남궁성의 정석코딩_자바의정석_기초편 유튜브