스트림(Stream) - 데이터의 연속적인 흐름

다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것

 

스트림이 제공하는 기능 중간 연산과 최종 연산

중간 연산 연산결과가 스트림인 연산. 반복적으로 적용 가능

최종 연산 연산결과가 스트림이 아닌 연산. 단 한번만 적용가능(스트림의 요소를 소모)

 

순서 : 스트림 만들기 중간 연산(0~n) 최종 연산(0~1)

String[] strArr = { "dd", "aaa", "CC", "cc", "b" };
Stream<String> stream = Stream.of(strArr);	// 문자열 배열이 소스인 스트림 (①)
Stream<String> filteredStream = stream.filter();	// 걸러내기(중간 연산) (②)
Stream<String> distinctedStream = stream.distinct(); // 중복제거(중간 연산) (②)
Stream<String> sortedStream = stream.sort();	// 정렬(중간 연산) (②)
Stream<String> limitedStream = stream.limit(5); // 스트림 자르기(중간 연산) (②)
int total = stream.count();	// 요소 개수 세기(최종 연산) (③)

 

스트림의 특징

스트림은 데이터 소스로부터 데이터를 읽기만할 뿐 변경하지 않는다.

List<Integer> list = Arrays.asList(3,1,5,4,2);
List<Integer> sortedList = list.stream().sorted().collect(Collectors.toList());
// list를 정렬해서 sortedList에 저장함
System.out.println(list);	// [3, 1, 5, 4, 2]
System.out.println(sortedList); // [1, 2, 3, 4, 5]

 

스트림은 Iterator처럼 일회용이다. (필요하면 다시 스트림을 생성해야 함)

strStream.forEach(System.out::println);	// 모든 요소를 화면에 출력(최종 연산)
int numOfStr = strStream.count();	// 에러, 스트림이 이미 닫혔음.

 

최종 연산 전까지 중간연산이 수행되지 않는다. - 지연된 연산

IntStream intStream = new Random().ints(1,46);	// 1~45 범위의 무한 스트림
intStream.distinct().limit(6).sorted()	// 중간 연산
	  .forEach(i->System.out.print(i+", ")); // 최종 연산

=> 중간연산은 해야할 일을 미리 적어놓듯이 일단 적어놓고, 최종 연산을 할 때 중간연산을 거쳐 최종 연산이 수행된다.

 

스트림은 작업을 내부 반복으로 처리한다.

for(String str : strList)
	System.out.println(str);
    
	↕
    
stream.forEach(System.out::println);	// 최종 연산

=> forEach()는 메소드 안으로 for문을 넣은 것이다. 수행할 작업은 매개변수로 받는다.

 

스트림의 작업을 병렬로 처리 병렬스트림

Stream<String> strStream = Stream.of("dd","aaa","CC","cc","b");
int sum = strStream.parallel() // 병렬 스트림으로 전환(속성만 변경)
		.mapToInt(s -> s.length()).sum();	 // 모든 문자열의 길이의 합

 

기본형 스트림 IntStream, LongStream, DoubleStream

- 오토박싱&언박싱의 비효율이 제거됨(Stream<Integer> 대신 IntStream사용)

- IntStream은 숫자와 관련된 유용한 메소드를 Stream<T>보다 더 많이 제공

 

스트림 만들기 컬렉션

Collection인터페이스의 stream()으로 컬렉션을 스트림으로 변환

(ListSet을 스트림으로 변환)

List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream();  // list를 스트림으로 변환

// 스트림의 모든 요소를 출력
intStream.forEach(System.out::print);	// 12345
intStream.forEach(System.out::print);	// 에러. 스트림이 이미 닫혔다.

 

 

ex)

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class Practice {
	
	public static void main(String[] args) {
		List<Integer> list = Arrays.asList(1,2,3,4,5);
		Stream<Integer> intStream = list.stream(); // list를 소스로 하는 Stream을 생성
		intStream.forEach(System.out::print); // 최종연산인 forEach가 끝나고 stream이 닫힌다.
		
		// stream은 1회용. stream에 대해 최종연산을 수행하면 stream이 닫힌다.
		intStream = list.stream(); // list로부터 stream을 다시 생성
		intStream.forEach(System.out::print);
		
	}

}

 

 

 

스트림 만들기 배열

 

객체 배열로부터 스트림 생성하기

Stream<T> Stream.of(T... values) // 가변 인자
Stream<T> Stream.of(T[])
Stream<T> Arrays.stream(T[])
Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive) // 배열의 일부(from~to)

 

기본형 배열로부터 스트림 생성하기

IntStream IntStream.of(int... values)
IntStream IntStream.of(int[])
IntStream Arrays.stream(int[])
IntStream Arrays.stream(int[] array, int startInclusive,int endExclusive)

 

 

스트림 만들기 임의의 수

난수를 요소로 갖는 스트림 생성하기

IntStream intStream = new Random().ints(); // 무한 스트림
intStream.limit(5).forEach(System.out::println); // 5개의 요소만 출력한다.
IntStream intStream = new Random().ints(5) // 크기가 5인 난수 스트림을 반환

 

* 지정된 범위의 난수를 요소로 갖는 스트림을 생성하는 메소드(Random클래스)

IntStream ints(int begin, int end)	// 무한 스트림
LongStream longs(long begin, long end)
DoubleStream doubles(double begin, double end)

IntStream ints(long streamSize, int begin, int end)	// 유한 스트림
LongStream longs(long streamSize, long begin, long end)
DoubleStream doubles(long streamSize, double begin, double end)

 

 

특정 범위의 정수를 요소로 갖는 스트림 생성하기(IntStream, LongStream)

IntStream IntStream.range(int begin, int end)
IntStream IntStream.rangeClosed(int begin, int end)
IntStream intStream = IntStream.range(1,5);	// 1,2,3,4
IntStream intStream2 = IntStream.rangeClosed(1,5);	// 1,2,3,4,5

 

스트림 만들기 람다식 iterate(), generate() [무한스트림]

 

람다식을 소스로 하는 스트림 생성하기

static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)	// 이전 요소에 종속적
static <T> Stream<T> generate(Supplier<T> s)	// 이전 요소에 독립적

 

iterate()는 이전 요소를 seed로 해서 다음 요소를 계산한다.

Stream<Integer> evenStream = Stream.iterate(0, n->n+2); // 0, 2, 4, 6, ...

 

generate()seed를 사용하지 않는다.

Stream<Double> randomStream = Stream.generate(Math::random);
Stream<Integer> oneStream = Stream.generate(()->1);

 

스트림 만들기 파일과 빈 스트림

 

파일을 소스로 하는 스트림 생성하기

Stream<Path> Files.list(Path dir)		// Path는 파일 또는 디렉토리
Stream<String> Files.lines(Path path) // 파일 내용을 라인단위로 읽어서 stream에 넣음
Stream<String> Files.lines(Path path, Charset cs)
Stream<String> lines() // BufferedReader클래스의 메소드

 

비어있는 스트림 생성하기

Stream emptyStream = Stream.empty();	 // empty()는 빈 스트림을 생성해서 반환한다.
long count = emptyStream.count();	// count의 값은 0

 

 

 

스트림의 연산 중간 연산

* map()과 flatMap()이 핵심

스트림의 연산 최종 연산

* reduce()와 collect()가 핵심

* forEachOrdered는 병렬스트림의 경우에 순서를 유지해줌

* findAny는 병렬일 때 filter와 함께 사용, findFirst는 직렬일 때 사용.

 

스트림의 중간연산

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

Stream<T> skip(long n) // 앞에서부터 n개 건너뛰기
Stream<T> limit(long maxSize) // maxSize 이후의 요소는 잘라냄
IntStream intStream = IntStream.rangeClosed(1,10); // 12345678910
intStream.skip(3).limit(5).forEach(System.out::print); // 45678

 

스트림의 요소 걸러내기 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
IntStream intStream = IntStream.rangeClosed(1, 10); // 12345678910
intStream.filter(i->i%2==0).forEach(System.out.::print);	// 246810
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);
 

스트림 정렬하기 - sorted()

Stream<T> sorted()	// 스트림 요소의 기본 정렬(Comparable)로 정렬
Stream<T> sorted(Comparator<? super T> comparator) // 지정된 Comparator로 정렬

Comparator의 comparing()으로 정렬 기준을 제공

comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)
studentStream.sorted(Comparator.comparing(Student::getBan))	// 반 별로 정렬
			 .forEach(System.out::println);

추가 정렬 기준을 제공할 때는 thenComparing()을 사용

thenComparing(Comparator<T> other)
thenComparing(Function<T, U> keyExtractor)
thenComparing(Function<T, U> keyExtractor, Comparator<U> keyComp)
studentStream.sorted(Comparator.comparing(Student::getBan)	// 반 별로 정렬
			 .thenComparing(Student::getTotalScore)	// 총점별로 정렬
             .thenComparing(Student::getName))	// 이름별로 정렬
             .forEach(System.out::println);

 

 

ex)

import java.util.Comparator;
import java.util.stream.Stream;

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;
	}
}

public 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)
					);
//		studentStream.sorted(Comparator.comparing((Student s) -> s.getBan())
		studentStream.sorted(Comparator.comparing(Student::getBan) // 1. 반 별 정렬
				.thenComparing(Comparator.naturalOrder()))  // 2. 기본 정렬
				.forEach(System.out::println);

	}

}

 

 

스트림의 요소 변환하기 - map()

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"));

map 메소드를 사용해서 Stream<File> 을 Stream<String>으로 바꾼다.

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

 

 

ex) 파일 스트림(Stream <File>) 에서 파일 확장자(대문자)를 중복없이 뽑아내기

fileStream.map(File::getName)	// Stream<File> -> Stream<String>
	.filter(s->s.indexOf('.')!=-1)	// 확장자가 없는 것은 제외함
    .map(s->s.substring(s.indexOf('.')+1)) // 파일이름전체-> 파일확장자만 남김
    .map(String::toUpperCase)	// 대문자로 변환
    .distinct() // 중복 제거
    .forEach(System.out::print);  // JAVABAKTXT

 

 

스트림의 요소를 소비하지 않고 엿보기 - 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::print); // JAVABAKTXT

(중간에 작업 결과 확인할 때 쓴다)

 

 

ex)

import java.io.File;
import java.util.stream.Stream;

public 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);
		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)) // 확장자만 추출
//				.peek(s->System.out.printf("extension=%s%n", s))
				.map(String::toUpperCase) 	// 모두 대문자로 변환
				.distinct()	// 중복 제거
				.forEach(System.out::print); // JAVABAKTXT
		
		System.out.println();

	}

}

 

 

 

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

Stream<String[]> strArrStrm = Stream.of(new String[]{"abc", "def", "ghi"}, 
					new String[]{"ABC", "GHI", "JKLMN"});
Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream);

 

 

ex)

 

import java.util.Arrays;
import java.util.stream.Stream;

public 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<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();
			

	}

}

출처 - 유튜브 남궁성의 정석코딩 [자바의 정석 - 기초편] 

+ Recent posts