[자바] 스트림
스트림
스트림 - 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의 해놓은 것이다.[ 스트림은 '데이터의 흐름' 이다 ]
-> 람다를 이용해서 코드의 양을 줄이고 간결하게 표현이 가능하다.
String[] strArr = { "aaa", "bbb", "ccc" };
List<String> strList = Arrays.asList(strArr);
// stream 생성
Stream<String> strStream1 = strList.stream()
Stream<String> strStream2 = Arrays.stream(strArr);
// 스트림 사용전
Arrays.sort(strArr);
Collections.sort(strList);
for(String str: strArr)
System.out.println(str);
for(String str: strList)
System.out.println(str);
// 스트림 사용후
strStream1.sorted().forEach(System.out::println);
strStream2.sorted().forEach(System.out::println);
메개변수에 대입된 람다식을 데이터 소스의 모든 요소에 적용
- 스트림은 데이터 소스를 변경하지 않는다
- 스트림은 Iterator 처럼 일회용이다 ( 한번 사용하고 다시 사용하고 싶으면 스트림을 다시 선언해야한다. )
- 내부 반복 : 반복문을 메서드의 내부에 숨길 수 있다.
최종연산이 수행되기 전까지는 중간연산이 수행되지 않는다
중간 연산 : 연산결과가 스트림인 연산. 스트림에 연속해서 중간연산 가능
최종 연산 : 연산결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능
stream.distinct().limit(5).sorted().forEach(System.out::println)
// 최종 - forEach
// 중간 - distinct, limit, sorted
요소의 타입이 T인 스트림은 기본적으로 Stream<T> 이지만, 비효율을 줄이기 위해 Stream<Integer> 대신에 IntStream, LongStream, DoubleStream을 제공한다.
병렬 스트림
parallel() 메서드를 호출해서 병렬로 연산을 수행 한다
sequential( ) 메서드를 쓰면 병렬이 되지 않게 처리하지만, 기본적으로 병렬처리를 하지 않기에 사용을 잘 안함
스트림 만들기
컬렉션
Collection에 stream()이 저장되어 있어, list & set 구현한 컬렉션 클래스 모든 생성 가능
Stream<T> Collection.stream()
List<Integer> list = Arrays.asList(1,2,3,4,5); // 가변인자
Stream<integer> intStream = list.stream(); // list를 소스로 하는 컬렉션 생성
intStream.forEach(System.out::println); // 스트림의 모든 요소 출력
배열
Stream<T> Stream.of(T... values) // 가변인자
Stream<T> Stream.of(T[])
Stream<T> Arrays.stream.of(T[])
Stream<T> Arrays.stream.of(T[] array, int startIncusive, int endExclusive)
IntStream IntStream.of(int... values) // 가변인자
IntStream IntStream.of(int[])
IntStream Arrays.stream(int[])
IntStream Arrays.stream(int[] array, int startIncusive, int endExclusive)
특절 범위의 정수
IntStream IntStream.range(int begin, int end) // end 부분 포함 안함
IntStream IntStream.rangeClosed(int begin, int end) // end 부분 포함
임의의 수
난수들로 이뤄진 스트림을 반환한다
IntStream ints()
LongStream longs()
DoubleStream doubles()
limit() 로 크기 제한을 안하면 무한 스트림 이다
유한 스트림 , limit(), 범위 지정 ( end는 범위 불포함 )
// limit()
IntStream intStream = new Randome().ints();
intStream.limit(5).forEach(System.out::println);
// 유한
IntStream ints(long streamSize)
LongStream longs(long streamSize)
DoubleStream doubles(long streamSize)
IntStream intStream = new Random().ints(5); // 크기가 5인 난수
// 범위 지정
IntStream ints(int begin, int end);
LongStream longs(long begin, long end);
DoubleStream doubles(double begin, double end);
람다시 - iterate(), generate()
Stream클래스의 iterate(), generate()는 람다식을 매개변수로 받아서, 이 람다식에 의해 계산되는 값들을 요소로 하는 무한 스트림 생성
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
// 지정된 값부터 시작해 람다식 f에 의해 계산된 결과를 다시 seed값으로 해서 결과 반복
Stream<Integer> evenStream = Stream.iterate(0, n->n+2);
static <T> Stream<T> generate(Supplier<T> s)
// generate는 Supplier<T> 이므로 매개변수가 없는 람다식만 허용 된다
// iterate와 달리 이전 결과를 이용해 다은 요소를 계산하지 않는다.
iterate(), gererate()에 의해 생성된 스트림은 기본형 스트림 타입의 참조변수로 다룰 수 없다.
파일
list() - 지정된 디렉토리에 있는 파일의 목록을 소스로 하는 스트림 생성
Stream<Path> Files.list(Path dir)
빈 스트림
요소가 하나도 없는 빈 스트림 생성가능하다 ( 스트림 연산을 수행한 결과가 하나도 없을때, null 보다는 빈 스트림 반환하기 )
Stream emptyStream = Stream.empty();
// empty()는 빈 스트림을 생성해 반환한다
두 스트림 연결
concat()을 사용해 두 스트림을 하나로 연결 ( 두 스트림의 요소는 같은 타입이여야 한다. )
String[] str1 = {"1","2","3"}
String[] str2 = {"A1","b","c"}
Stream<String> str12 = Stream.of(str1);
Stream<String> str22 = Stream.of(str2);
Stream<String> str32 = Stream.concat(str12,str22);
스트림 자르기
skip() - 스트림 요소 건너뛰기
limit() - 크기 제한
Stream<T> skip(long n);
Stream<T> limit(long maxSize);
IntStream skip(long n);
IntStream limit(long maxSize);
intStream.skip(3).limit(5).forEach(System.out::print); // 3개 건너뛰고 5개로 크기 제한
스트림 요소 걸러내기
distinct() - 중복 요소 제거
filter() - 주어진 조건에 맞지 않는 요소 걸러냄 ( 매개변수로 Predicate를 필요로 하는데, 연산결과가 boolean 람다식도 가능하고, 다른 조건으로 여러번 사용 가능 )
Stream<T> filter(Predicate<? super T>predicate)
Stream<T> distinct()
intStream.distinct().forEach();
intStream.filter(i->i%2==0).filter(i->i%3==0).forEach();
정렬
sorted() 사용 - Comparator로 스트림 정렬, ( Comparator대신, int 값을 반환하는 람다식도 가능, 단, 스트림 요소가 Comparator을 구현한 클래스가 아니면 예외 발생 )
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
정렬에 사용되는 메서드는 많지만, 기본적인 메서드는 comparing()이며 정렬 조건 추가시에는 thenComparing 이다
comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)
thenComparing(Comparator<T> other)
thenComparing(Function<T, U> keyExtractor)
thenComparing(Function<T, U> keyExtractor, Comparator<U> kepComp)
studentStream.sorted(Comparator.comparing(Student::getBen).thenComparing(Student::getName).forEach();
변환 - map()
스트림 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환할때 사용
Stream<R> map(Function<? super T, ? extends R> mapper)
조회 - peek()
연산과 연산 사이에 올바르게 처리되었는지 확인, 요소를 소모하지 않으므로 여러번 써도 가능하다. ( filter(), map() 결과 확인시 유용)
fileStream.map(File::getName).filter().peek().map().peek().forEach()
// ( ) 사이에는 식들이 존재
mapToInt(), mapToLong(), mapToDouble()
map()은 연산결과를 Stream<T>타입의 스트림을 반환하는데, 스트림의 요소를 숫자로 변환하는 경우 IntStream 같은 기본형 스트림으로 변환하는 것이 더 유용하다.
Stream<Integer> studentScoreStream = studentStream.map(Student::getTotalScore);
->
IntStream studentScoreStream = studentStream.mapToInt(Student::getTotalScore);
Stream<String> -> IntStream 변환할때, mapToInt(Integer::parseInt)
Stream<Integer> -> IntStream 변환할때, mapToInt(Integer::intvalue)
flatMap() - Stream<T[]>를 Stream<T>로 변환
스트림의 요소가 배열이거나 map()의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream<T[]>인 경우, Stream<T>로 다루는 것이 더 편리하다. -> 이때 flatMap 사용
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);
Stream<String> strStrm = strArrStrm.flatmap(Arrays::stream);
// 드 물긴 하지만 2개의 스트림을 하나로 합칠떄도 사용
Stream<String> strStrm = Stream.of("a","b");
Stream<String> strStrm2 = Stream.of("d","c");
Stream<Stream<String>> strmStrm = Stream.of(strStrm,strStrm2);
Optional<T> , Optionallnt
최종연산의 결과 타입이 Optional인 경우가 있다.
Optional<T>은 제네릭 클래스로 'T타입의 객체'를 감싸는 래퍼 클래스이다. 그래서 Optional타입의 객체에는 모든 타입의 참조변수를 담을 수 있다.
public final class Optional<T> {
private final T value; // T 타입의 참조변수
...
}
Optional 객체 생성하기
of( )
ofNullable( ) - null일 가능성이 있다면 이 메소드 사용하기
참조변수를 기본값으로 초기화 할때는 empty() 사용하기
String str = "abc";
Optional<String> optVal = Optional.of(str);
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(new String("abc"));
Optional<String> optVal = Optional.of(null); // NullPointerException 발생
Optional<String> optVal = Optional.ofNullable(null);
optional 객체 값 가져오기
get( ) - 저장된 값을 가져올 때 사용하기
orElse( ) - 값이 null일때를 대비함. 대체할 값을 지정 가능
orElse( ) 변형 - orElseGet() : null을 대체할 값을 반환하는 람다식을 지정할 수 있다
orElse( ) 변형 - orElseThrow() : null일때 예외발생
Optional<String> optVal = Optional.of("abc");
String str1 = optval.get();
String str1 = optval.orElse(""); // null 일시 "" 반환
T orElseGet(Supplier<? extends T> other)
T orElseThrow(Supplier<? extends T> exceptionSupplier)
String str2 = optval.orElseGet(String::new); // () -> new String() 과 동일
String str3 = optval.orElseThrow(NullPointerException::new); // 널이면 예외 발생
OptionalInt, OptionalLong, OptionalDouble
Optional도 기본형을 값으로 하는 OptionalInt, OptionalLong, OptionalDouble 반환한다
최종 연산
최종 연산 - 스트림의 요소를 소모해서 결과를 만들어 냄 ( 더 이상 사용 불가능, 결과 값은 합 과 같은 단일 값 or 배열 or 컬렉션 )
forEach()
void forEach(Consumer<? super T> action)
allMatch(), anyMatch(), noneMatch(), findFirst(), findAny()
스트림의 요소에 대해 지정된 조건에 모든 요소가 일치하는지, 일부가 일치하는지, 일치하지 않는지 확인한다
메서드들 모두 매개변수로 Predicate를 요구하며, 연산결과로 boolean을 반환한다
boolean allMatch (Predicate<? super T> predicate)
boolean anyMatch (Predicate<? super T> predicate)
boolean noneMatch (Predicate<? super T> predicate)
boolean noFailed = stuStream.anyMatch(s->s.getTotalScore() <= 100 )
findFirst() - 스트림의 요소중 조건에 일치하는 것 첫번째 것을 반환
주로 filter( ) 와 사용되며, 병렬스트림인 경우에는 findFirst( ) 대신에 findAny() 사용하기
Optional<Student> stu = stuStream.filter(s -> s.getTotalScore() <= 100).findFirst();
Optional<Student> stu = parallelStream.filter(s -> s.getTotalScore() <= 100).findAny();
모두 반환타입은 Optional<T> 이며, 스트림의 요소가 없을 때는 비어있는 Optional객체를 반환한다
count(), sum(), average(), max(), min()
기본형 스트림에는 스트림의 요소들에 대한 통계정보를 얻을 때 쓰는 메서드들이다
long count()
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
reduce() - 리듀싱
스트림의 요소를 줄여나가면서 연산을 수행하고 최종결과를 반환한다. ( 매개변수 타입이 BinaryOperator<T> 이다 )
Optional<T> reduce(BinaryOperator<T> accumulator)
연산결과의 초기값을 갖는 reduce( ) 도 있다.
메서드들은 초기값과 스트림의 첫번째 요소로 연산이 시작된다.
스트림의 요소가 없는 경우, 초기값이 반환되므로, 반환타입이 Optional<T>가 아니라 T 이다.
reduce()를 사용하는 방법은 초기값과 어떤 연산(BinaryOperator)으로 스트림의 요소를 줄여나갈 것인지만 결정하면 된다.
collect()
스트림의 요소를 수집하는 최종 연산으로 리듀싱(reducing) 과 유사하다
collect() 스트림의 최종연산, 매개변수로 컬렉터를 필요로 한다
Collector 인터페이스. 컬렉터는 이 인터페이스를 구현해야 한다
Collectors 클래스, static메서드로 미리 작성된 컬렉터를 제공한다
collect()는 매개변수의 타입이 Collector인데, 매개변수가 Collector를 구현한 클래스의 객체여야 한다.
Object collect(Collector collector) // collector를 구현한 클래스의 객체를 매개변수로
스트림을 컬렉션과 배열로 변환 - toList(), toSet(), toMap(), toCollection(), toArray()
스트림의 모든 요소를 컬렉션에 수집하려면, Collectors 클래스의 toList()와 같은 메서드를 사용하면 된다.
List, Set이 아닌 특정 컬렉션을 지정하려면, toCollection() 에 해당 컬렉션의 생성장 참조를 매개변수로 넣으면 된다
List<String> names = stuStream.map(Student::getName).collect(Collectors.toList());
ArrayList<String> list = names.stream().collect(Collectors.toCollection(ArrayList::new));
map은 키 - 값을 쌍으로 저장해야 한다
Map<String,String> map = personStream.collect(Collectors.toMap(p->p.getRegId(), p->p);
스트림에 지정된 요소들을 'T[]' 타입의 배열로 변환하려면 toArray()를 사용하면 된다.
// 2방법이 있다
Student[] stuNames = studentStream.toArray(Student[]::new);
Object[] stuNames = studentStream.toArray();
통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy()
리듀싱 - reducing()
문자열 결합 - joining()
문자열 스트림의 모든 요소를 하나의 문자열로 반환한다 ( 구분자, 접미사, 접두사 가능 )
스트림의 요소가 String, StringBuffer일 경우에 가능하고 문자열이 아니면 map()으로 먼저 요소를 문자열로 반환해야한다
map 없이 하고 싶으면, 스트림 요소에 toString()을 호출하면 된다
String studentNames= stuStream.map(Student::getName).collect(joining());
String studentNames= stuStream.map(Student::getName).collect(joining(","));
String studentNames= stuStream.map(Student::getName).collect(joining(",","[","]"));
// map 없이
String studentInfo = stuStream.collect(joining(","));
그룹화 - groupingBy(), partitoningBy()
groupingBy() - 스트릠의 요소들 Function으로 분류
partitionBy() - 스트릠의 요소들 Predicate으로 분류 ( 더 빠름 )
그룹화 분할의 결과는 Map 에 담겨서 반환된다
ParitioningBy( )
// 기본분할
Map<Boolean, List<Student>> stuBySex = stuStream.collect(partitioningBy(Student::isMale));
// 기본 + 통계 -> 남녀 수
Map<Boolean, Long> stuNumBySex = stuStream.collect(partitioningBy(Student::isMale, counting()));
// 기본 + 통계 -> 남녀 각 총점
Map<Boolean, Long> stuNumBySex = stuStream.collect(partitioningBy(Student::isMale, summingLong()));
// 2중 분할
Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex =
stuStream.collect( partitioningBy(Student::isMale,
partitioningBy(s -> s.getScore() < 150)
)
);
groupingBy( )
Map<Integer, List<Student>> stuByBan = stuStream.collect(groupingBy(Student::getBan)); // toList() 생략
paritioningBy 와 같이 다중 그룹화가 가능하다
Collector 구현하기
컬렉션을 작성한다 = Collector인터페이스 구현
public interface Collector<T, A, B> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A,R> finisher();
Set<Characteristics> Characteristics(); // 컬렉터의 특성이 담긴 set 반환
}
supplier() - 작업 결과를 저장할 공간을 제공
accumulator() - 스트림의 요소를 수집할 방법을 제공
combiner() - 두 저장공간을 병합할 방밥을 제공 ( 병렬 스트림 )
finisher() - 결과를 최종적으로 변환할 방법을 제공
Characteristics
Characteristics.CONCURRENT 병렬로 처리할 수 있는 작업
Characteristics.UNODERED 스트림의 요소의 순서가 유지될 필요가 없는 작업
Characteristics.IDENTITY_FINISH finisher()가 항등 함수인 작업
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(
Collerctor.Characteristics.CONCURRENT,
Collerctor.Characteristics.UNORDERED,
));
}