* 스트림(Stream)
- 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것 / 데이터의 연속적인 흐름 이란 의미를 가지고 있음.
CollectionFrameWork에서 List, Set, Map 사용 방식을 표준화하려 했으나 실패!
이유: List, Set과 Map 은 사용 방식이 다르기 때문.
이를 Stream으로 통일! 진정한 표준화.
다양한 데이터 소스로부터 Stream을 만들 수 있고, 작업 방식이 같음.
중간 연산을 n번 거치고 최종 연산을 통해 결과를 얻을 수 있음.
- 스트림으로 변환(생성)
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream(); // 컬렉션.
Stream<String> strStream = Stream.of(new String[]{"a", "b", "c"}); // 배열
Stream<Integer> evenStream = Stream.interate(0, n->n+2); // 0,2,4,6..
Stream<Double> randomStream = Stream.generate(Math::random); // 람다식
IntStream intStream = new Random().ints(5); // 난수 스트림(크기가 5)
- 작업순서
1. 스트림 만들기
2. 중간 연산 (여러 번 가능) (연산 결과가 스트림, 반복적으로 적용 가능)
3. 최종 연산 (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(); // 요소 개수 세기 (최종연산)
* 스트림의 특징
1. 데이터를 읽기만 할 뿐 변경하지 않는다.
2. 스트림은 Iterator처럼 일회용이다. (필요하면 다시 스트림을 생성해야 함)
3. 최종 연산 전 까지 중간 연산이 수행되지 않는다. - 지연된 연산
IntStream intStream = new Random().ints(1,46); // 1~45 범위의 무한 스트림
intStream.distinct().limit(6).sorted() // 중간 연산
// 무한 스트림에서 distinct() (중복제거)를 하는게 말이 안되지만, 가능하다! 지연된 연산을 하기 때문..
.forEach(i-> System.out.print(i)); // 최종 연산
4. 작업을 내부 반복으로 처리한다.
for (String str : strList)
System.out.println(str);
// 이런 반복문을 내부 반복으로 처리할 수 있음
stream.forEach(System.out::println);
// 이유: forEach 메서드에 for문이 들어있어서.
void forEach(Consumer<? super T> action){
Objects.requireNonNull (action); // 매개변수의 널 체크
for (T t: src) // 내부 반복(fo문을 메서드 안으로 넣음)
action.accept(T);
// 어찌보면 비효율적일 수 도 있지만, 코드가 간결해져서 알아보기 편해짐!
5. 스트림의 작업을 병렬로 처리 - 병렬 스트림 (멀티쓰레드)
Stream<String> strStream = stream.of("dd", "aaa", "CC", "cc", "b");
int sum = strStream.parallel() // 병렬 스트림으로 전환 (속성만 변경)
.mapToInt(s -> s,length()).sum(); // 모든 문자열의 길이의 합
6. 기본형 스트림 - IntStream, LongStream, DoubleStream
Stream<T> 에서 T에 기본형은 못 들어가고, 참조형 만 들어갈 수 있음.
그래서 기본형을 넣으면 오토 박싱으로 변환해서 Stream에 넣게 됨.
이런 비효율을 제거하고자 Stream<Integer> 대신 IntStream 사용
숫자와 관련된 유용한 메서드를 Stream<T> 보다 더 많이 제공!
* 스트림 만들기
- Collection의 Stream 생성: Collection 인터페이스의 stream()으로 컬렉션을 스트림 생성.
Stream<E> stream() // Collection인터페이스의 메서드
- 배열의 Stream 생성: 객체 배열로부터 스트림 생성하기
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 endExculsive)
Stream<String> strStream = Stream.of("a", "b", "c"); // 가변인자
Stream<String> strStream = Stream.of(new String[]{"a", "b"});
Stream<String> strStream = Arrays.stream(new String[]{"a", "b"});
Stream<String> strStream = Arrays.stream(new String[]{"a", "b"},0, 2);
- 기본형 배열의 Stream 생성: 기본형 배열로부터 스트림 생성하기
IntStream InStream.of(int...values) ... 이하 Stream 과 동일
- 난수를 요소로 갖는 스트림 생성하기
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) // 무한 스트림
IntStream ints(long streamSize, int begin, int 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 intStream = 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<T> distinct() | 중복을 제거 |
| Stream<T> filter(Perdicate<t> perdicate) | 조건에 안맞는 요소 제외 |
| Stream<T> limit(ling maxSize) | 스트림의 일부 잘라냄 |
| Stream<T> skip(long n) | 스트림의 일부 건너뜀 |
| Stream<T> peek(Consumer<T> action) | 스트림 요소에 작업 수행 |
| Stream<T> sorted() Stream<T> sorted(Comparator<T> comparator) |
스트림의 요소를 정렬 |
| Stream<R> map(Function<T,R> mapper) | 스트림의 요소를 변환 |
| Stream<R> flatMap(Function<T, Stream<R>> mapper) |
| 최종 연산 | 설명 |
| void forEach(Consumer<? super T> action void forEachOrdered(Consumer <? supet T> action) |
각 요소에 지정된 작업 수행 순서를 유지할 때, 병렬 스트림 사용할 때 사용 |
| long count() | 스트림의 요소 개수 반환 |
| Optional<T> max(Comparator<? super T> comparator) Optional<T> min(Comparator<? super T> comparator) |
스트림의 최대값, 최소값 반환 |
| Optional<T> findAny() // 아무거나 하나 - 병렬 Optional<T> findFirst() // 첫 번째 요소 - 직렬 |
스트림의 요소 하나 반환 |
| boolean allMatch(Perdicate<T> p) // 모두 만족하는지 boolean anyMatch(Perdicate<T> p) // 하나라도 만족하는지 boolean noneMatch(Perdicate<T> p) // 모두 만족하지 않는지 |
주어진 조건을 만족하는지 확인 |
| Object[] toArray() A[] toArray(IntFunction<A[]> generator) |
스트림의 모든 요소를 배열로 반환 |
| Optional<T> reduce(BinaryOperator<T> accumulator) T reduce(T identity, BinaryOperator<T> accumulator) U reduce(U identity, BiFunction<U, T, U> accumulator, BinartOperator<U> combiner) |
스트림의 요소를 하나씩 줄여가면서 계산 |
| R collect(Collector<T,A,R> collector) R collect(Supplier<R> supplier, BiConsumer<R,T> accumulator, Biconsumer<R,R> comviner) |
스트림의 요소를 수집. 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환 |
* 스트림의 중간 연산
1. 스트림 자르기 - skip(), limit()
IntStream IntStream = IntStream.rangeClosed(1,10); // 1,2,3,4,5,6,7,8,9,10
intStream.skip(3).limit(5).forEach(System.out::print); // 45678
2. 스트림의 요소 걸러내기 - filter(), distinct()
IntStream inStream - 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
3. 스트림 정렬하기 - sorted()
studentStream.sorted(Comparator.comparing(Student::getBan))
.forEach(System.out::println);
// 람다식으로 변경하기
studentStream.sorted(Comparator.comparing((Student s) -> s.getBan())
- 추가 정렬 기준을 제공할 때는 thanComparing()을 사용
studentStream.sorted(Comparator.comparing(Student::getBan) // 반별로 정렬
.thenComparing(Student::getTotalScore) // 총점별로 정렬
.thenComparing(Student::getName)) // 이름별로 정렬
.forEach(System.out::println);
4. 스트림의 요소 변환하기 - map()
Stream<File> fileStream = Stream.of(... 파일 객체들);
Stream<String> filenameStream = fileStream.map(File::getName); // 파일 이름으로 스트림 요소 변환
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::println); // 출력
5. 스트림의 요소를 소비하지 않고 엿보기 - peek()
- forEach와 기능은 같음. 중간중간 작업 결과 확인하기 위해 사용.
Stream<File> fileStream = Stream.of(... 파일 객체들);
Stream<String> filenameStream = fileStream.map(File::getName); // 파일 이름으로 스트림 요소 변환
fileStream.map(File::getName) // Stream<File> --> Stream<String>
.filter(s -> s.indexOf('.') != -1) // 뒤에 확장자 없으면 제외
.map(s -> s.substring(s.indexOf('.')+1)) // 뒤쪽만 잘라서 넣기
.peek(s->System.out.println(s); // 변환 상태 확인
.map(String::toUpperCase) // 대문자로 변경
.distinct() // 중복 제외
.forEach(System.out::println); // 출력
6. 스트림의 스트림을 스트림으로 변환 -flatMap()
Stream<String[]> strArrStrm = Stream.of(new String[]{"abc", "def", "ghi"},
new String[]{"ABD", "DEF", "GHI"});
위 스트림에서 map을 사용하면,
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);
스트림 속에 스트림이 있는 형태로 변환됨. 이게 아니라, 두 배열이 합쳐진 스트림을 얻고 싶을 때 flatMap() 사용
Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream);
* 스트림의 최종 연산
1. 스트림의 모든 요소에 지정된 작업을 수행 - forEach(), forEachOrdered()
IntStream.range(1, 10).sequential() .forEach(System.out::print); //123456789 (직렬스트림)
IntStream.range(1, 10).parallel() .forEach(System.out::print); //683295714 (병렬스트림)
IntStream.range(1, 10).parallel() .forEachOrdered(System.out::print); //123456789 (병렬스트림)
2. 조건 검사 - allMatch(), anyMatch(), nonMatch()
boolean hasFailedStu = stuStream.anyMatch(s->s.getTotalScore()<=100);
3. 조건에 일치하는 요소 찾기 - findFirst(), findAny()
Optional<Student> result = stuStream.filter(s->s.getTotalScore() <= 100).findFirst();
4. 스트림의 요소를 하나씩 줄여가며 누적 연산 수행 (accumulate) - reduce()
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)
/*
identity - 초기값
accumulator - 이전 연산결과와 스트림의 요소에 수행할 연산
combiner - 병렬처리된 결과를 합치는데 사용할 연산 (병렬 스트림)
int count = intStream.reduce(0, (a,b) -> a+1);
int sum = intStream.reduce(0, (a,b) -> a+b);
int max = intStream.reduce(Integer.MIN_VALUE, (a,b) -> a>b ? a:b);
int min = intStream.reduce(Integer.MAX_VALUE, (a,b) -> a<b ? a:b);
5. collect()와 Collectors
- collect()는 Collector를 매개변수로 하는 스트림의 최종 연산
Object collect(Collector collector) // Collector를 구현한 클래스의 객체를 매개변수로
** reduce와 collect의 차이 : Stream 전체에 작업할 땐 reduce / Stream을 그룹별로 작업할 땐 collect
- Collector는 collect에 필요한 메서드를 정의해 놓은 인터페이스
public interface Collector<T, A, R>{ // T를 A에 누적한 다음, 결과를 R로 변환해 반환
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinartOperator<A> combiner();
Function<A, R> finisher();
Set<Characterustucs> characteristics();
...
}
accumulator() 연산 (누적 방법)을 supplier()에 저장한 다음 combiner()로 합치고, finisher()로 변환.
- Collectors 클래스는 다양한 기능의 컬렉터를 제공
변환 - mapping(), toList(), toSet(), toMap(), toCollection()
통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy(), summarizingInt()
문자열 결합 - joining()
리듀싱 - reducing()
그룹화와 분할 - groupingBy(), partitioningBy(), collectingAndThen()
* Collectors 활용
- 스트림을 컬렉션으로 변환 - toList(), toSet(), toMap(), toCollection()
List<String> names = stuStream.map(Student::getName)
.collect(Collectors.toList());
ArrayList<String> list = names.stream()
.collect(Collectors.toCollection(ArrayList::new));
Map<String, Person> map = personStream
.collect(Collectors.toMap(p->p.getRegId(), p->p));
- 스트림을 배열로 변환 - toArray()
Student[] stuNames = studentStream.toArray(Student[]::new);
- 스트림의 통계정보 제공 - counting(), summingInt(), averagingInt(), maxBy(), minBy(), summarizingInt()
long count = stuStream.count();
long count = stuStream.collect(counting()); // 그룹별로 작업 수행 가능함! .count 와는 다르다
- 스트림을 리듀싱 - reducing()
reduce()와 동일한 기능이지만, Collectors의 reducing() 은 그룹별로 가능해짐.
- 문자열 스트림의 요소를 모두 연결 - joining()
String studentNames = stuStream.map(Student::getName).collect(joining());
String studentNames = stuStream.map(Student::getName).collect(joining(",")); // 구분자
String studentNames = stuStream.map(Student::getName).collect(joining(",";"[","]"));
String studentInfo = stuStream.collect(joining(",")); // student의 toString()으로 결합
* 스트림의 그룹화와 분할
- partitioningBy()는 스트림을 2분할한다. (Collectors 에 있는 메서드!)
Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downstream)
Map<Boolean, List<Student>> stuBySex = stuStream
.collect(partitioningBy(Student::isMale)); // 남학생은 true
List<Student> maleStudent = sutBySex.get(true); // Map 에서 남학생 목록 얻기
Map<Boolean, Long> stuBySex = stuStream
.collect(partitioningBy(Student::isMale, counting())); // true 에 남학생 수 저장
Long maleStudent = sutBySex.get(true); // Map 에서 남학생 수 얻기
Map<Boolean, Optional<Student>> stuBySex = stuStream
.collect(partitioningBy(Student::isMale, maxBy(comparingInt(Student::getScore))));
// 남,여학생 1등 Map 에 저장
Map<Boolean, Map<Boolean, List<Student>>> stuBySex = stuStream
.collect(partitioningBy(Student::isMale, // 1. 성별로 분할
partitioningBy(s-> s.getScore() <150))); // 2. 성적으로 분할
List<Student> failedMaleStu = stuBySex.get(true).get(true); // 불합격 남자
List<Student> failedFemaleStu = stuBySex.get(false).get(true); // 불합격 여자
- groupingBy() 는 스트림을 n분할한다. (Collectors 에 있는 메서드!)
Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
Map<Integer, List<Student>> stuByBan = stuStream
.collect(groupingBy(Student::getBan, toList()));
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream
.collect(groupingBy (Student::getHak, // 1. 학년별 그룹화
groupingBy(Student::getBan))); // 2. 반별 그룹화
Map<Integer, Map<Integer, Set<Student.Level>>> stuByHakAndBan = stuStream
.collect(
groupingBy(Student::getHak, groupingBy(Student::getBan,
mapping(s-> {
if (s.getScore() >=200) return Student.Level.HIGH;
else if (s.getScore() >=100) return Student.Level.MID;
else return Student.Level.LOW;
}, toSet()) // mapping()
))
);
'코딩 > JAVA 공부' 카테고리의 다른 글
| [Modern Java] Functional Interface (0) | 2022.12.08 |
|---|---|
| [객체지향] 의존성을 이용해 설계 진화시키기 (0) | 2022.12.03 |
| [JAVA] 자바의 정석 _ Optional<T> (0) | 2022.10.28 |
| [JAVA] 자바의 정석 _ java.util.function 패키지 (0) | 2022.10.28 |
| [JAVA] 자바의 정석 _ 람다식/ 함수형 인터페이스/ 메서드 참조 (0) | 2022.10.24 |