다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것.
데이터소스란?
컬렉션이나 배열과 같이, 여러 데이터를 저장하고 있는 것들을 이야기함.
다양한 데이터 소스를 다루는 것이, 진정한 통일을 이룸.
다양한 데이터 소스로부터, "스트림"을 만들 수 있음.
중간연산(n번) -> 최종연산(1번)
스트림 생성하는 법
스트림 : 데이터의 연속적인 흐름
스트림이 제공하는 기능 : 중간 연산과 최종 연산
1. 스트림 만들기
2. 중간 연산 (0번 ~ n번) : 연산 결과가 스트림인 연산. 반복적으로 적용 가능.
3. 최종 연산 (1번) : 연산 결과가 스트림이 아닌 연산. 단 한번만 적용 가능하다. (스트림 요소를 소모함)
4. 결과 얻음
// Collection에는 stream() 메서드가 있다.
Stream<T> Collection.stream() // 스트림으로 반환
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.iterate(0, n -> n + 2);
Stream<Double> randomStream = Stream.generate(Math::random);
IntStream intStream = new Random().ints(5);
// 중복제거 5개자르기 정렬 출력
stream.distinct().limit(5).sorted().forEach(System.out::println)
//////{----------중간연산-----------}{----------최종연산----------}]
String[] strArr = { "dd", "aaa", "CC", "cc", "b" };
Stream<String> stream = Stream.of(strArr); // 1. 문자열 배열이 소스인 스트림 생성
// 2. 중간연산
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(); // 3. 요소 개수 세기 (최종연산)
스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐, 변경하지 않는다.
스트림은 Iterator처럼 일회용이다. 필요하면 다시 스트림을 생성해야 한다.
최종연산 시, 스트림의 요소를 하나하나 꺼내어 "소모"하기 때문에, 최종 연산 작업이 끝나면 스트림이 닫인다.
strStream.forEach(System.out::println); // 모든 요소를 화면에 출력 (최종 연산)
int numOfStr = strStream.count(); // 에러. 스트림이 이미 닫혔기 때문에, 실행되지 않음.
최종 연산 전 까지, 중간 연산이 수행되지 않는다. - 지연된 연산
// 난수발생 스트림 -> 난수를 끝도 없이 줌. "무한 스트림"
IntStream intStream = new Random().ints(1, 46); // 1~45 범위의 무한 스트림
intStream.idtinct().limit(6).sorted() // 중간 연산
.forEach(i -> System.out.print(i + ",")); // 최종 연산
-> 중간 연산을 체크만 해두고 있다가, 나중에 연산함.
스트림은 작업을 내부 반복으로 처리한다.
for (String str : strList)
System.out.println(str);
// 위 코드 보다는, 아래 한줄로 처리함! 성능은 비 효율적이나, 코드가 간결해진다!
stream.forEach(System.out::println);
스트림의 작업을 병렬로 처리 : 병렬 스트림
Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
int sum = strStream.parallel() // 병렬 스트림으로 전환 (속성만 변경)
.mapToInt(s -> s.length()).sum(); // 모든 문자열의 길이의 합
Collection 인터페이스의 stream()으로 컬렉션을 스트림으로 변환
Stream<E> stream() // collection 인터페이스의 메서드
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); // 에러. 스트림이 이미 닫혔음.
객체 배열로부터 스트림 생성하기
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. index
// ex
Stream<String> strStream = Stream.of("a", "b", "c");
Stream<String> strStream = Stream.of(new String[] {"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[] {"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[] {"a", "b", "c"}, 0, 3);
기본형 배열로부터 스트림 생성하기 (기본형 스트림)
IntStream IntStream.of(int... values) // Stream이 아니라 IntStream
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 class 안에 정의되어 있는 메서드의 범위 (전 범위)
Integer.MIN_VALUE <= ints() <= Integer.MAX_VALUE
Long.MIN_VALUE <= longs() <= Long.MAX_VALUE
0.0 <= doubles() < 1.0
// 지정된 범위의 난수를 요소로 갖는 스트림을 생성하는 메서드(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) // begin <= x < end
IntStream IntStream.rangeClosed(int begin, int end) // begin <= x <= end
IntStream intStream = IntStream.range(1, 5); // 1, 2, 3, 4
IntStream intStream = IntStream.rangeClosed(1, 5); // 1, 2, 3, 4, 5
람다식을 소스로 하는 스트림 생성하기
iterate() : 이전 요소를 seed로 해서, 다음 요소를 계산한다.
값이 있어야 람다식을 계산 할 수 있기 때문에, 초기값을 설정해줘야 한다.
계속 값을 생산하는 무한스트림
generate() : seed를 사용하지 않는다. (초기값 필요 없음!)
각 요소가 서로 독립적이다. (이전 요소와 서로 상관이 없음)
// 람다식을 이용해서 계속 스트림을 만들어낸다. -> 무한스트림!
// ↓ 초기값
static<T> Stream<T> iterate(T seed, UnaryOperator<T> f) // 이전 요소에 종속적
static<T> Stream<T> generate(Supplier<T> s) // 이전 요소에 독립적
Stream<Integer> evenStream = Stream.iterate(0, n->n+2);
Stream<Double> randomStream = Stream.generate(Math::random); // 랜덤 값 생성
Stream<Integer> oneStream = Stream.generate(() -> 1); // 계속 1이 나오는 무한스트림
파일을 소스로 하는 스트림 생성하기
로그파일 분석 or 다량의 텍스트 파일 분석
비어있는 스트림 생성
Stream<Path> Files.list(Path dir) // Path : file or directory
// lines() : 파일 내용을 라인 단위로 읽어서, String 으로 만든 후, String Stream으로 만듦
Stream<String> Files.lines(Path path)
Stream<String> Files.lines(Path path, Cahrset cs)
Stream<String> lines() // BufferedReader의 메서드
// 비어있는 스트림 생성
Stream emptyStream = Stream.empty(); // empty()는 빈 스트림을 생성해서 반환
long count = emptyStream.count(); // count 값은 0
스트림이 제공하는 기능 : 중간 연산과 최종 연산
Comparator : 정렬 기준을 제공해준다. 제공해주지 않으면 객체가 가진 기본 정렬 기준 사용.
중간 연산
출처 : 자바의정석 남궁성 선생님 유튜브
- 핵심 : Map & flatMap
최종 연산
출처 : 자바의정석 남궁성 선생님 유튜브
- forEachOrdered(Consumer< ? super T > action) : 순서 유지. 병렬 스트림으로 처리할 때에도, 순서를 유지하게 해주는 것
- Optional<T> : Wrapper 클래스 -> 어떤 타입의 값(작업 결과)을 감싸고 있는 클래스 -> 래퍼클래스 참고자료
- findAny() : 병렬 스트림에서 사용. // filter(조건)랑 같이 쓰임
- findFirst() : 직렬 스트림에서 사용. // filter(조건)랑 같이 쓰임
- 핵심 : reduce(sum, count...) & collect(그룹화하여 계산)
sorted()에 파라미터로 넘겨줄 수 있는 Comparator들
Comparator의 comparing()으로 정렬 기준 제공
comparing(Function< T, U > keyExtractor)
comparing(Function< T, U > keyExtractor, Coumparator< U > KeyComparator)
comparing()의 반환 타입은 Comparator!
studentStream.sorted(Comparator.comparing(Student::getBan)).forEach(System.out::println); // 반별로 정렬
studentStream.sorted(Comparator.comparing(Student::getBan)
.thenComparing(Student::getTotalScore)
.thenComparing(Student::getName))
.forEach(System.out::println);
스트림의 요소 변환하기
ex)
-------------- map(File::getName) : File 객체의 file 이름을, String으로 변환 후 반환
Stream< File > ------------------> Stream< String >
타입을 변환할 수 있을 뿐만 아니라, 요소의 값을 하나 하나 변환할 수 있다.
Stream<R> map(Function<? super T, ? extends R> mapper) // Stream<T> -> Stream<R>
// ex
Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1"), new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt"); // file Stream
// getName이 파일 이름을 문자열로 반환. (File f) -> f.getName() // 결과가 String
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println); // 스트림의 모든 파일의 이름을 출력
// ex2 : 파일 확장자(대문자)를 중복없이 뽑아내기
fileStream.map(File::getName) // Stream<File> -> Stream<String>
.filter(s -> s.indexOf('.') != -1) // '.'이 없는 파일 이름(확장자 x)은 제외. indexOf는 해당 값이 없을 경우, -1을 반환한다.
.map(s -> s.substring(s.indexOf('.') + 1)) // Stream<String> -> Stream<String>. substring(s.indexOf('.') + 1) : '.' 뒤의 문자부터 끝까지 잘라냄. index를 넘겨줘야 함.
.map(String::toUpperCase) // Stream<String> -> Stream<String> 모두 대문자로 변경
.distinct() // 중복 제거
.forEach(System.out::print); // JAVABAKTXT
forEach()랑 똑같은 역할을 하지만, peek()은 중간연산자이기 때문에, 스트림을 소비하지 않는다!
즉, Stream< T > 를 반환함!
작업 중간 중간, 잘 수행이 되었는지 확인할 때 사용. (디버깅 용도)
Stream<T> peek(Consumer<? super T> action) // 스트림 소비 x
void forEach(Consumer<? super T> action) // 스트림 소비 o
fileStream.map(File::getName)
.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::println);
// 문자열 배열 스트림
Stream<String[]> strArrStrm = Stream.of(new String[] {"abc", "def", "ghi"},
new String[] {"ABC", "GHI", "JKLMN"});
// 배열을 Stream<String>으로 바꾸고자 함.
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream); // 배열을 스트림으로 바꿈.
// Arrays::stream == (arr) -> Arrays.stream(arr)
// 배열을 받아, 스트림으로 변환 : String[] -> Stream<String>
// 즉, Stream<String[]> -> Stream<Stream<String>> 이 되어버린다.
// 위와 같이 할 경우, Stream 안의 Stream이 되어버림.
// 의도한것은 Stream<String[]> -> Stream<String> 이었는데,
// Stream<String[]> -> Stream<Stream<String>>이 되어버렸음.
// 이럴 경우, flatMap()을 사용해야 함.
Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream); // 여러개의 문자열 배열을, 하나의 문자열 배열인 것 처럼 변환!
// Stream<Stream<String>> -> Stream<String>
public final class Optional<T> {
private final T value; // T 타입의 참조 변수 : 모든 종류의 객체 저장 가능. null도 가능
}
null을 직접 다루는 것은 위험하다. (NullPointerException이 발생할 수 있음)
-> 객체 안에 담아서 간접적으로 null을 다루기 위해 래퍼클래스 사용.
null을 직접 다루려면, null 체크 필수. (if문 필수) -> 코드가 지저분해짐.
Optional을 이용해서, null을 Optional 객체에 넣어서 다룸. 값이 null이더라도, 참조 변수에는 null이 아닌, 객체의 주소가 들어가있음. -> 항상 null이 아님 -> 위의 두 가지 문제점이 사라짐.
null을 직접 다루지 않는 객체 : String(초기화를 ""로 함. 길이가 0인 char[] 배열), 각종 배열들(멤버 변수. int[] arr = new int[0];)
Optional<T> 객체 생성
null 대신, 빈 Optional<T> 객체를 사용하자.
String str = "abc";
Optional<String> optVal = Optional.of(str); // optVal -> Optional(= str) -> "abc"
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(null); // NullPointerException 발생
Optional<String> optVal = Optional.ofNullable(null); // OK
Optional<String> optVal = null; // 바람직하지 않은 초기화 방식.
Optional<String> optVal = Optional.<String>empty(); // 빈 객체로 초기화. <String>생략 가능
Optional 객체의 값 가져오기
get(), orElse(), orElseGet(), orElseThrow()
isPresent() : Optional 객체의 값이 null이면 false, 아니면 true
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get(); // optVal에 저장된 값을 반환. null이면 예외 발생
★ String str2 = optVal.orElse(""); // optVal에 저장된 값이 null일 때는, ""를 반환.
★ String str3 = optVal.orElseGet(String::new); // 람다식 사용 가능. () -> new String()
String str4 = optVal.orElseThrow(NullPointerException::new); // null이면 예외 발생 (예외 종류 지정 가능)
// isPresent
if(Optional.ofNullable(str).isPresent()) { // str으로 만든 Optional 객체의 값이 null인가? -> null이 아닐 때만 작업 수행
System.out.println(str);
}
// ifPresent(Consumer) - null이 아닐 때만 작업 수행, null이면 아무것도 안함.
Optional.ofNullable(str).ifPresent(System.out::println);
public final class OptionalInt {
...
private final boolean isPresent; // 값이 저장되어 있으면 true
private final int value; // int 타입의 변수
}
// 빈 Optional 객체와의 비교
OptionalInt opt = OptionalInt.of(0); // OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empty(); // OptionalInt에 0을 저장
// 0이 저장된 객체와, empty()가 적용된 객체 간의 구별을 어떻게 함? -> isPresent 이용
opt.isPresent() : true
opt2.isPresent() : false
opt.equals(opt2) : false
forEach(Consumer<? super T> action) // 병렬 스트림인 경우, 순서가 보장되지 않음. 더 빠름.
forEachOrdered(Consumer<? super T> action) // 병렬 스트림인 경우에도 순서 보장
// ↓ 직렬 스트림
IntStream.range(1, 10).sequential().forEach(System.out::print); // 123456789
IntStream.range(1, 10).sequential().forEachOrdered(System.out::print); // 123456789
// ↓ 병렬 스트림 (여러 쓰레드 나눠서 작업)
IntStream.range(1, 10).parallel().forEach(System.out::print); // 683295714 (순서 보장 x)
IntStream.range(1, 10).parallel().forEachOrdered(System.out::print); // 123456789 (순서 보장 o)
// ↓ 조건식
allMatch(Predicate<? super T> predicate) // 모든 요소가 조건을 만족시키면 true
anyMatch(Predicate<? super T> predicate) // 한 요소라도 조건을 만족시키면 true
noneMatch(Predicate<? super T> predicate) // 모든 요소가 조건을 만족시키지 않으면 true
// ex
hasFailedStu = stuStream.anyMatch(s -> s.getTotalScore() <= 100); // 낙제자가 있는지?
// 결과가 null일 수 있기 때문에, Optional로 반환
Optional<T> findFirst() // 첫 번째 요소를 반환. 순차 스트림에 사용.
Optional<T> findAny() // 아무거나 하나를 반환. 병렬 스트림에 사용.
// ex
Optional<Student> result = stuStream.filter(s -> s.getTotalScore() <= 100).findFirst(); // filter랑 같이 씀. 조건을 만족하는 첫번째 요소 반환.
Optional<Student> result = parallelStream.filter(s -> s.getTotalScore() <= 100).findAny(); // filter랑 같이 씀. 조건을 만족하는 아무거나 하나를 반환.
// 복습.
BinaryOperator<T> : Type T의 인자 두개를 받고, 동일한 Type T 객체를 리턴.
Optional<T> reduce(BinaryOperator<T> accumulator) // identity가 없을 수 있으므로 Optional로 반환. 아래 식과 동일한것.
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U, T, U> accumulator, BinaryOperator<U> combiner)
// ex : int reduce(int identity, IntBinaryOperator op)
// a = identity, b = stream의 요소
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);
// (a, b) -> a + 1 과 (a, b) -> a + b 의 구동 방식. 내부적으로 다음과 같이 수행됨.
int a = identity; // 누적 결과 저장 변수. (초기화)
for (int b : stream)
a = a + 1; // (a, b) -> a + 1 == count()
a = a + b; // (a, b) -> a + b == sum()
collect()는 Collector를 매개변수로 하는 스트림의 최종 연산
reduce() vs collect()
Collector는 수집(collect)에 필요한 메서드를 정의해놓은 인터페이스이다.
Collectors 클래스는 다양한 기능의 컬렉터(Collector를 구현한 클래스)를 제공
우리가 직접 구현 할 필요가 없다!
Object collect(Collector collector) // Collector를 구현한 클래스의 객체를 매개변수로
Object collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) // 잘 안쓰임
public interface Collector<T, A, R> { // T(요소)를 A에 누적한 다음, 결과를 R로 변환해서 반환
Supplier<A> supplier(); // StringBuilder::new -> 누적할곳 (저장 공간)
BiConsumer<A, T> accumlator(); // (sb, s) -> sb.append(s) -> 누적 방법
BinaryOperator<A> combiner(); // (sb1, sb2) -> sb1.append(sb2) -> 결합 방법(병렬)
Function<A, R> finisher(); // sb -> sb.toString() -> 최종 변환
Set<Characteristics> characteristics(); // 컬렉터의 특성이 담긴 Set 반환
}
복습! : 컬렉션 -> List, ArrayList, Set, HashSet...
스트림을 컬렉션으로 변환 : toList(), toSet(), toMap(), toCollection()
List<String> names = stuStream.map(Student::getName) // Stream<Student> -> Stream<String>
.collect(Collectors.toList()); // Stream<String> -> List<String>
ArrayList<String> list = names.stream()
.collect(Collectors.toCollection(ArrayList::new)); // Stream<String> -> ArrayList<String>
Map<String, Person> map = personStream // 사람 객체 -> 주민번호 -> key , 사람 객체 -> 사람 객체 -> value (항등 함수)
.collect(Collectors.toMap(p -> p.getRegId, p-> p)) // Stream<Person> -> Map<String, Person>
// -> key : 주민번호, value : Person p 를 저장 (객체의 주소)
long count = stuStream.count(); -> 요소의 개수 확인 (전체 카운팅. 스트림의 모든 요소)
long count = stuStream.collect(Collectors.counting()); -> 그룹 별 카운팅 가능!
long totalScore = stuStream.mapToInt(Student::getTotalScore).sum(); -> IntStream의 sum()
long totalScore = stuStream.collect(summingInt(Student::getTotalScore)); // 그룹별 sum 가능!!!
OptionalInt topScore = studentStream.mapToInt(Student::getTotalScore).max();
Optional<Student\> topStudent = stuStream.max(Comparator.comparingInt(Student::getTotalScore));
Optional<Student\> topStudent = stuStream.collect(maxBy(Comparator.comparingInt(Student::getTotalScore)));
-> Comparator: 비교 기준. 비교 기준으로 Student::getTotalScore를 넘겼음.
스트림을 리듀싱 : reducing() -> 그룹별 리듀싱!
reduce()는 전체 리듀싱만 가능하다.
리듀싱 : sum(), count()... 등
BinaryOperator<T> op : accumulator
Collector reducing(BinaryOperator<T> op);
Collector reducing(T identity, BinaryOperator<T> op);
Collector reducing(U identity, Function<T, U> mapper, BinaryOperator<U> op); // map + reduce. 변환 작업이 필요할 경우, 3번째 방식 사용.
// Function<T, U> mapper : 변환 작업
IntStream intStream = new Random().ints(1, 46).distinct().limit(6);
OptionalInt max = intStream.reduce(Integer::max); // 전체 리듀싱
Optional<Integer> max = intStream.boxed().collect(reducing(Integer::max)); // 그룹 별 리듀싱 가능!
// 초기값 | 누적 작업
long sum = intStream.reduce(0, (a, b) -> a + b);
long sum = intStream.boxed().collect(reducing(0, (a, b) -> a + b));
int grandTotal = stuStream.map(Student::getTotalScore).reduce(0, Integer::sum));
int grandTotal = stuStream.collect(reducing(0, Student::getTotalScore, Integer::sum));
// 문자열 스트림의 요소를 모두 연결
// joining() = Collectors.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()으로 결합