컬렉션이나 배열에 데이터를 담고 for문이나 Iterator를 쓰면 코드의 가독성과 재사용성이 떨어진다. 스트림을 통해 컬렉션이나 배열에 함수 여러개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있다. 또한 람다를 이용해 코드의 양을 줄이고 간결하게 표현할 수 있다.
즉, 컬렉션과 배열을 함수형으로 처리할 수 있다.
스트림은 데이터 소스를 변경하지 않는다. 데이터 소스로부터 데이터를 읽기만 할 뿐 데이터 소스는 변경하지 않는다. 필요하다면 정렬된 결과를 배열이나 컬렉션에 담아서 반환할 수 있다.
스트림은 일회용이다. 한번 사용하면 닫혀서 다시 쓸 수 없다. 필요하면 다시 생성해야 한다.
스트림은 작업을 내부 반복으로 처리한다. 내부반복은 반복문을 메서드 내부에 숨겼다는 것을 의미한다.
지연된 연산. 최종 연산이 수행되기 전 까지 중간연산이 수행되지 않는다. 중간에 sort()같은 연산이 있어도 즉각적으로 수행되지 않는다. 최종연산이 수행되어야 스트림의 요소들이 중간연산을 거쳐 최종연산에서 소모된다.
병렬처리가 가능하다. 병렬처리는 하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행하는 것을 말한다. 쓰레드를 이용해 많은 요소를 빠르게 처리할 수 있다. 스트림에 parallel()이라는 메서드를 호출해 병렬로 연산을 수행하게 하면 병렬연산이 된다.
sequential()을 호출하면 병렬처리 기능을 끌 수 있다.
타입을 지정할때 기본적으로 Stream<T> 지만 IntStream, LongStream, DoubleStream이 기본적으로 제공되고 이를 활용하는 것이 효율적이다.
컬렉션, 배열, 임의의 수 등을 소스로 스트림을 생성할 수 있다.
stream()
이 정의되어있어 List와 Set을 구현한 컬렉션 클래스는 모두 이 메서드로 스트림을 생성할 수 있다.List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> stream1 = list.stream(); // 스트림 생성
stream1.forEach(System.out::println);
Arrays.stream()
Stream<String> stream2 = Stream.of("a", "b","c");
Stream<String> stream3 = Arrays.stream(new String[]{"a", "b","c"});
IntStream IntStream.of(1,2,3,4,5);
임의의 수
난수를 생성하는데 쓰이는 Random클래스에는 난수로 이루어진 스트림을 반환하는 메서드가 있다.
이 메서드들은 크기가 정해지지 않은 무한 스트림이므로 limit()로 스트림의 크기(개수)를 제한해주어야 한다. limit()를 쓰지 않고 ints(5) 처럼 파라미터로 안에 넣어도 된다.
IntStream : ints()
LongStream : longs()
DoubleStream : doubles()
IntStream.range(int begin, int end) 로 쓰면 begin<= x < end 범위의 정수가 스트림 안으로 들어간다.
generate(), iterate() 람다식
static <T> Stream <T> generate(Supplier<T> s)
람다식에 의해 계산되는 값들을 요소로 하는 무한스트림 생성
static <T> Stream <T> iterate(T seed, UnaryOperator<T> f)
seed부터 시작해서 람다식 f에 의해 계산된 결과를 반복. 이전결과를 이용해 다음 요소 계산
ex) Stream<Integer> stream4 = Stream.iterate(20, n -> n+2).limit(3); 20,22,24
빈 스트림. 스트림 연산 수행 결과가 없을때 null보다 빈스트림을 반환하는 것이 좋다.
Stream emptyStream = Stream.empty();
builder() 빌더를 사용하면 원하는 값을 직접 넣을 수 있다.
Stream<String> builderStream = Stream.<String>builder().add("java").add("hi").build();
스트림의 연산을 이용하면 복잡한 작업을 간단히 처리할 수 있다.
중간연산은 연산 결과가 스트림이라 연속해서 사용할 수 있다.
skip(long n)은 처음 n개를 건너 뛰고 limit(long maxSize)는 요소를 maxSize로 제한
IntStream intStream = IntStream.rangeClosed(1,10); // 1~10
intStream.skip(3).limit(4).forEach(System.out::print); // 4567
distinct()는 중복요소 제거. filter()는 주어진 조건(Predicate)에 맞지 않는 요소 제거
IntStream intStream = IntStream.of(1,2,2,3,3,4,4,5,6,7,8);
intStream.distinct().filter(i -> i%2==0).forEach(System.out.print); // 2468
지정된 Comparator로 스트림을 정렬. Coparator 대신 int 반환하는 람다식 사용 가능.
Coparator 미지정시 기본 정렬 기준으로 정렬. 문자열은 사전순(대문자우선알파벳순). 정수는 오름차순
.sorted(Comparator.reverseOrder())
: 기본정렬의 역순 정렬
// 학생을 반별, 성적순, 이름순으로 정렬해 출력하는 스트림
studentStream.sorted(Comparator.comparing(Student::getBan)
.thenComparing(Student::getTotalScore)
.thenCompating(Student::getName)
.forEach(System.out::println));
원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때 사용
// 파일이 요소로 들어있는 스트림에서 확장자만 뽑은 다음 중복 제거후 출력
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) // TXTJPGJAVA
연산 사이에 올바르게 처리되었는지 확인 가능.
최종 연산이 아니므로 연산 사이에 여러번 끼워도 문제가 문제는 안된다.
.peek(s -> System.out.printf("파일이름=%s%n", s)) 식으로 사용할 수 있다.
스트림의 타입이 Stream<T[]> 처럼 배열인 경우 Stream<T>로 변환할 수 있다.
Stream<String[]> 을 .flatMap(Arrays::stream)으로 Stream<String>으로 변환 가능.
최종연산은 스트림의 요소를 소모해서 결과를 만들어 낸다. 따라서 최종 연산은 한번만 가능하고 그 뒤에 스트림의 재사용은 불가능하다.
forEach()
peek()과 달리 스트림의 요소를 소모하는 최종연산이다.
반환타입이 void라 주로 스트림의 요소를 출력하는 용도로 사용된다.
스트림의 요소에 대해 지정된 조건에 요소들이 일치하는지 확인하는데 사용한다.
매개변수로 Predicate, 연산 결과로 boolean을 반환한다.
boolean allMatch (Predicate) // 모든 요소가 일치하면 true
boolean anyMatch (Predicate) // 하나라도 일치하면 true
boolean noneMatch (Predicate) // 모든 요소가 불일치면 true
// 모든 학생이 80점 넘었는지 확인
boolean success = stuStream.allMatch(s->s.getScore()>=80)
스트림의 요소를 하나씩 줄여나가면서 연산을 수행하고 최종 결과를 반환
매개변수의 타입이 BinaryOperator<T>다.
처음 두 요소로 연산한 결과를 다음 요소와 연산해 하나씩 줄여나간다.
int count = intStream.reduce(0, (a,b) -> a+1); // count()
int sum = intStream.reduce(0, (a,b) -> a+b); // sum()
int max = intStream.reduce(Integer.MIN_VALUE, (a,b) -> a>b ? a : b) // max()
컬렉터(Collector)로 정의 된 방법으로 스트림의 요소를 수집해 최종연산을 수행한다.
최종 연산중 가장 복잡하지만 유용하게 활용될 방법이 많다.
Collector : 인터페이스. 컬렉터는 이 인터페이스를 구현해야 한다.
Collectors : 클래스. static 메서드로 미리 작성된 컬렉터
collect() : 스트림의 최종연산. 매개변수로 컬렉터를 필요로 한다.
~~.collect(Collectors.toList()); 처럼 사용해서 컬렉션으로 반환하게 할 수 있다.
List<String> names = stuStream.map(Student::getName()) // toList()로 리스트 변환
.collect(Collectors.toList());
TreeSet<Integer> ids = stuStream.map(Student::getId()) // toCollection(생성자)로 treeset으로 변환
.collect(Collectors.toCollection(TreeSet::new));
Map<String,Person> map1 = personStream // map은 key-value 각각 할당해서 변환
.collect(Collectors.toMap(p->p.getId(), p->p));
.partitioningBy()
두개로 나눌때는 분할이 빠르다.// 학생들을 성별로 분할한 뒤 통계정보를 추가
Map<Boolean, Long> stuNumBySex = stuStream
.collect(partitioningBy(Student::isMale, counting()));
System.out.println(stuNumBySex.get(true)); // 남학생 수
System.out.println(stuNumBySex.get(false)); // 여학생 수
.groupingBy()
여러개로 나눌 때 쓴다.// 학생들을 성적 상중하 그룹으로 분류후 통계 정보 추가
Map<Student.Level, Long> stuByLevel = stuStream
.collect(groupingBy(s -> {
if(s.getScore>=80) return Student.Level.HIGH;
else if(s.getScore >= 60) return Student.Level.MID;
else return Student.Level.Low;
}, counting()));
System.out.println(stuByLevel.get(Student.Level.HIGH)); // 상반 학생 수
Collectors.counting()으로 collect()안에서 count()처럼 카운트를 셀 수 있다.
Collectors.reducing()으로 collect()안에서 reduce()처럼 요소를 줄여나가며 연산을 할 수 있다.
Collectors.joining()으로 문자열 스트림의 모든 요소를 하나의 문자열로 연결해서 반환할 수 있다.