⦁ 스트림(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()으로 컬렉션을 스트림으로 변환
(List와 Set을 스트림으로 변환)
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
⦁ 스트림의 연산 – 중간 연산

⦁ 스트림의 연산 – 최종 연산

* 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();
}
}
출처 - 유튜브 남궁성의 정석코딩 [자바의 정석 - 기초편]
'Java > Java의 정석' 카테고리의 다른 글
220406 Java - Chapter 15. 입출력 I/O (0) | 2022.04.06 |
---|---|
220402 Java - Chapter 14. 람다와 스트림(Lambda & Stream) Part 3 (0) | 2022.04.02 |
220330 Java - Chapter 14. 람다와 스트림(Lambda & Stream) Part 1 (0) | 2022.03.30 |
220329 Java - Chapter 13. 쓰레드(Thread) 남은 부분 (0) | 2022.03.29 |
220328 Java - Chapter 13. 쓰레드(Thread) (0) | 2022.03.29 |