데이터를 저장하는 Collections, Arrays, Map등 여러 타입들이 존재한다. 이를 모두 포괄하여 다룰 수 있는 Stream
이라는 인터페이스가 등장하였다. 데이터소스들을 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의해 두었다.
다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
스트림으 사용 순서는 다음과 같다.
public class playGround{
public static void play() {
Integer[] intArr = new Integer[]{5, 2, 3, 1, 4, 7};
List<Integer> arrayList = Arrays.asList(intArr);
Stream<Integer> integerStream = arrayList.stream(); //arrayList에 대한 스트림 생성
integerStream.sorted().forEach(System.out::print); //스트림을 정렬하고, 출력
System.out.println();
arrayList.forEach(System.out::print);
}
}
//123457
//523147
실행 결과처럼 arrayList
를 통해 스트림을 생성한 뒤 정렬을 하였지만, arrayList
의 데이터는 달라지지 않았다.
스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐, 변경하지 않는다.
스트림의 결과가 필요하다면,
List<Integer> sortedList = integerStream.sorted().collect(Collectors.toList());
//123457
collect
라는 메서드를 통해 저장할 수 있다.
public class playGround{
public static void play() {
Integer[] intArr = new Integer[]{5, 2, 3, 1, 4, 7};
List<Integer> arrayList = Arrays.asList(intArr);
Stream<Integer> integerStream = arrayList.stream();
List<Integer> sortedList = integerStream.sorted().collect(Collectors.toList());
//최종연산 .collect후 스트림이 닫힘
integerStream.sorted().forEach(System.out::print);
//ERROR
//stream has already been operated upon or closed
}
}
최종 연산이 이루어진 스트림은 다시 사용할 수 없다.
새로 생성하여 사용하여 사용할 수 있다.
stream //스트림 생성
.distinct() // //
.limit(5) // 중간 연산 //
.sorted // //
.forEach(System.out::print); // 최종 연산 //
//stream.distinct().limit(5).sorted.forEach(System.out::print);
distinct()
: 중복 제거
filter(Predicate<T> predicate)
: predicate
조건에 안 맞는 요소 제외
limit(long maxsize)
: 스트림의 일부를 잘라낸다. 최대 크기가 maxsize
skip(long n)
: 스트림의 일부를 건너 뛰기
peek(Consumer<T> action)
: 스트림의 요소에 action
작업 수행
sorted()
: 정렬
sorted(Comparator<T> comparator)
: comparator
에 맞게 정렬
map()
: 스트림의 요소 반환
flatMap()
forEach(Comsumer<? super T> action)
forEachOrdered(Comsumer<? super T> action)
: action
을 수행
count()
: 요소의 개수 반환
max()
: 스트림의 최대값
min()
: 최솟값 반환
collect()
: 스트림의 요소를 수집
reduce()
: 요소를 하나씩 줄여가면서 계산
스트림은 중간 연산과 최종 연산이 존재한다. 하지만 연산의 순서가 존재한다. 스트림의 중간 연산들은 할 일 목록처럼 저장되었다가 최종 연산이 수행되면서 요소들이 저장된 중간 연산들을 거치고 최종 연산에서 소모된다. 이러한 흐름을 지연된 연산이라고 한다.
보통 스트림을 생성할 땐, 아래 코드처럼 T
라는 타입 변수를 통해 타입을 선언 한 뒤 생성 할 수 있다.
Stream<T> Collection.stream();
스트림은 다양한 타입의 요소를 지원하기 때문에, 특정 타입에서는 효율적인 부분이 떨어 질 수 있다. 기본형 (int, Long, double, ...)을 사용하려면, 래퍼클래스를 통해 생성되어야 하고, 오토박싱과 언박싱으로 인한 비효율이 증가할 수 있다.
그래서 기본형에 대해 사용할 수 있는 기본형 스트림이 존재한다.
IntStream, LongStream, DoubleStream, ...
기본형 스트림은 해당 기본형타입의 요소에서만 사용가능하지만, 더 효율적이고 해당 기본형에 사용하기 유용한 메서드들이 포함되어 있으므로 사용하는 것이 좋다.
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);// 0,1,2번째 요소만 {"a", "b", "c"}
//기본형 스트림
IntStream intStream = IntStream.of(1,2,3,4);
IntStream intStream = IntStream.of(new int[]{1,2,3,4});
IntStream intStream = Arrays.stream(new int[]{1,2,3,4});
IntStream intStream = Arrays.stream(new int[]{1,2,3,4}, 0, 2); //0, 1번째 요소만 {1,2}
IntStream intStream = IntStream.range(1,5); //1 ~ 4 {1,2,3,4}
IntStream intStream = IntStream.rangeClosed(1,5); //1 ~ 5 {1,2,3,4,5}
IntStream intStream = Random.ints(); //난수 생성
IntStream intStream = Random.ints(1, 9); //1 ~ 9 사이의 난수 생성
//.ints()는 무한 스트림이므로 stream을 연산하여 크기를 제한하여야 한다.
intStream.limit(5).forEach(System.out::println);
iterate()
, generate()
Stream클래스의 iterate()
, generate()
는 람다식을 받아 계산되는 값들을 요소로 하는 무한 스트림을 생성.
예시)
//iterate()
Stream<Integer> eventStream1 = Stream.iterate(0, n -> n + 2);
//0을 시작으로, n -> n + 2 라는 람다의 반환값을 저장하는 요소로하는 무한 스트림
//반환된 값을 다시 사용.
//0 + 2 -> 2
//2 + 2 -> 4
//4 + 2 -> 6
//6 + 2 -> ...
//0, 2, 4, 6, 8, ...
eventStream1.limit(5).forEach(System.out::println);
//0 2 4 6 8
//generate
Stream<Integer> eventStream2 = Stream.generate(() -> 1);
//1을 반환하는 무한 스트림
//generate는 매개변수가 없는 람다식만 허용한다.
eventStream2.limit(5).forEach(System.out::println);
//1 1 1 1 1
스트림의 중간연산은 여러번 가능하다.
제공되는 여러 메서드들을 통해 스트림을 조작해보도록 하자
.skip()
Stream<T> skip(long n)
//n만큼 건너뛰어 스트림 생성
IntStream intStream = IntStream.range(1, 10); // 1~9
intStream.skip(4).forEach(System.out::print);
//5 6 7 8 9
.limit()
Stream<T> limit(long maxsize)
//스트림의 요소를 maxsize로 제한
Stream<String> stringStream = Arrays.stream(new String[]{"aa", "bb", "cc", "dd", "ff"});
stringStream.limit(4).forEach(System.out::print);
//aabbccdd
.filter()
Stream<T> filter(Predicate<? super T> predicate)
//predicate식에 맞지 않는 식 거르기
Stream<String> stringStream = Arrays.stream(new String[]{"ab", "cde", "f", "gh", "ij"});
stringStream.filter(s -> s.length() == 2).forEach(System.out::println);
//ab
//gh
//ij
.distinct()
Stream<T> distinct()
//중복 제거
Stream<String> stringStream = Arrays.stream(new String[]{"A", "C", "B", "C", "C", "A"});
stringStream.distinct().forEach(System.out::println);
//A
//C
//B
.sorted()
Stream<T> sorted()
//기본 정렬
Stream<T> sorted(Comparator<? super T> comparator)
//comparator기준에 맞는 정렬
//기본 정렬
Stream<String> stringStream = Arrays.stream(new String[]{"A", "C", "B", "C", "C", "A"});
stringStream.sorted().forEach(System.out::print);
//AABCCC
//역순 정렬
Stream<String> stringStream = Arrays.stream(new String[]{"A", "C", "B", "C", "C", "A"});
stringStream.sorted(Comparator.reverseOrder()).forEach(System.out::print);
//CCCBAA
//길이로 정렬
Stream<String> stringStream = Arrays.stream(new String[]{"ab", "cde", "f", "gh", "ij"});
stringStream
.sorted(Comparator
.comparing(String::length)
.reversed())
.forEach(System.out::println);
//cde
//ab
//gh
//ij
//f
class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
}
public class playGround{
public static void play() {
//Perons 스트림 생성
Stream<Person> personStream = Stream.of(
new Person(23, "Tom"),
new Person(23, "Alice"),
new Person(38, "Royce"),
new Person(25, "Andrew")
);
personStream
.sorted(Comparator.comparing((Person s) -> s.getAge()) //나이순으로 정렬
.thenComparing((Person s) -> s.getName())) //같은 나이면, 이름 순으로 정렬
.forEach(System.out::println);
}
}
/*
Person{age=23, name='Alice'}
Person{age=23, name='Tom'}
Person{age=25, name='Andrew'}
Person{age=38, name='Royce'}
*/
.map()
Stream<R> map(Function<? super T, ? extends R> mapper)
//mapper수행 후 R타입으로 반환
IntStream intStream = IntStream.of(new int[]{1, 2, 3, 4, 5, 6, 7, 8});
intStream.map(a -> a * 2).forEach(System.out::print);
//2 4 6 8 10 12 14 16
스트림을 반환하는 중간연산과는 달리, 최종연산을 수행하면 그 스트림을 소모한다.
그래서 최대 한 번만 가능하다.
.forEach()
void forEach(Consumer<? super T> action)
void forEachOrdered(Consumer<? super T> action) //병렬스트림인 경우 순서를 보장해서 실행 함
IntStream.range(1,10).forEach(System.out::print); //123456789
IntStream.range(1,10).forEachOrdered(System.out::print); //123456789
IntStream.range(1,10).parallel().forEachOrdered(System.out::print); //682149357
.allMatch()
, .anyMatch()
, .nonMatch()
boolean allMatch (Predicate<? super T> predicate) //모든 요소가 조건을 만족하면 true
boolean anyMatch (Predicate<? super T> predicate) //하나 이상의 요소가 조건을 만족하면 true
boolean noneMatch (Predicate<? super T> predicate) //모든 요소가 조건을 만족하지 않으면 true
boolean isUnderAll = IntStream.range(1,10).allMatch(num -> num <= 10); //true
boolean isUnderAny = IntStream.range(1,10).anyMatch(num -> num <= 5); //true
boolean isUnderNone = IntStream.range(1,10).noneMatch(num -> num >= 10); //true
.reduce()
스트림의 요소를 줄여나가면서 누적 연산을 수행하고 최종결과 반환.
T reduce(T identity, BinaryOperator<T> accumulator) //identity라는 초기값에 대해 accumulator 작업 수행
Optional<T> reduec(BinaryOperator<T> accumulator) //identity가 null의 가능성이 있기 때문에 Optional로도 처리할 수 있다.
//.count()
int count = intStream.reduce(0, (a,b) -> a + 1);
//.sum()
/*
int a = identity; //identity
for(int b : stream) { a = a + b; } // accumulator
*/
int sum = intStream.reduce(0, (a,b) -> a + b);
//.max
int max = intStream.reduce(Integer.MAX_VALUE, (a,b)-> a > b ? a : b);
//.min
int min = intStream.reduce(Integer.MIN_VALUE, (a,b)-> a < b ? a : b);
.collect()
그룹별 reducing이 필요한 경우에 사용. Collector인터페이스를 구현한 Collectors클래스의 메서드를 자주 사용한다.
Object collect(Collector collector) //Collector를 구현한 클래스의 객체를 매개변수로 사용
List<String> names = studentStream.map(Student::getName)
.collect(Collectors.toList());
//Collectors.toList()를 통해 List로 변환
ArrayList<String> list = names.stream()
.collect(Collectors.toCollection(ArrayList::new));
//Collectors.toCollection(ArrayList::new)을 통해 컬렉션으로 저장
Map<String,Person> map = personStream
.collect(Collectors.toMap(p -> p.getRegId(), p -> p));
//Collectors.toMap()을 통해 Map의 key와 value를 선택, 저장
Student[] stuNames = studentStream.toArray(Student[]::new);
//.toArray() 처럼 매개변수 없이 사용 가능하지만 Object타입으로 변환함