자바

[자바] 스트림

j9972 2022. 7. 25. 16:59
728x90

스트림

스트림 - 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의 해놓은 것이다.[ 스트림은 '데이터의 흐름' 이다 ]

-> 람다를 이용해서 코드의 양을 줄이고 간결하게 표현이 가능하다.

 

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,
    ));
}