Today
-
Yesterday
-
Total
-
  • Stream
    Language/Java 2023. 12. 1. 00:01

    스트림이란?

    배열, 컬렉션 대상으로 연산을 수행

    컬렉션은 기본으로 stream()을 호출할 수 있다.

    배열은 Arrays 이용!

    일관성 있는 연산으로 자료의 처리를 쉽고 간단하게 한다.

    자료 처리에 대한 추상화가 구현되었다고 한다.

    한번 생성하고 사용한 스트림은 재사용 할 수 없음

    스트림 연산은 기존 자료를 변경하지 않는다.

    자료에 대한 스트림을 생성하면 스트림이 사용하는 메모리 공간은 별도로 생성되므로

    연산이 수행되도 기존 자료에 대한 변경은 발생하지 않는다.

    스트림 연산은 중간 연산과 최종 연산으로 구분된다.

    중간 연산은 여러 개의 연산이 적용될 수 있지만,

    마지막 연산은 단 하나만 적용할 수 있다.

    최종 연산이 호출되어야 중간 연산에 대한 수행이 이루어 지고 , 그 결과가 만들어진다.

    이런 연산을 지연 연산이라고 하는데,

    lazy init도 필요시 호출에 의해 초기화 되는 것 처럼

    스트림도 마지막 연산시 중간 연산을 호출하는 lazy 연산이다.

    마지막 연산을 하지 않으면 중간 연산을 수행되지 않는다.


    IntStream

    int[] -> IntStream

    int배열을 IntStream으로 변경 해보는 작업

    int[] arr = {1, 2, 3, 4, 5};
    
    for (int num : arr) {
        System.out.println(num); 
    }
    // 출력 
    // 1
    // 2
    // 3
    // 4
    // 5
    IntStream is = Arrays.stream(arr);
    is.forEach(System.out::println); // is가 여기서 소모되었기 때문에 재사용 불가
    // 출력 
    // 1
    // 2
    // 3
    // 4
    // 5

    Arrays.stream(int[] arr)IntStream을 반환한다. foreach()는 Consumer 인터페이스를 인자로 받기 때문에, 반환값이 없다. ( void Consumer<T> )

    그렇기 때문에 System.out::println을 작성해주면 foreach()로 값이 들어올때 마다 바로 출력한다.

    참고로 System.out::println 은 Consumer 인터페이스 구현을 람다식으로 표현한 n -> System.out.println(n) 을 메서드 참조형 문법으로 변환한 형태이다.

    Arrays.stream(arr).forEach(System.out::println);

    보통 이런 형태로 쓰인다.

    중간 연산자

    잘 쓰이는 중간연산자 몇개만 살펴보고 넘어간다. 중간 연산자는 너무 많기 때문에 stream 사용시 ctrl + space의 도움을 받아 그때 그때 필요한 연산자를 골라서 사용할 줄 아는 것도 필요하다고 배움!

    filter

    filter는 매개변수로 Predicate<T, boolean>을 받는다. Predicate는 T를 받아서 boolean을 반환하는 함수형 인터페이스!

    간단한 사용 예시

    private static long count(int[] arr) {
        // 2보다 큰 수만 카운트
    
        // boolean filter(Predicate<T>)
        return Arrays.stream(arr)
                .filter(n -> n > 2)
                .count();
    }

    map

    map은 매개변수로 UnaryOperator<T>를 받는다. UnaryOperator는 T를 받아서 T를 반환하는 함수형 클래스!

    간단한 사용 예시

    private static long sum2(int[] arr) {
        // map으로 배열 요소들 각각에 2를 곱해서 반환 후 sum() 연산
    
        // T map(UnaryOperator<T>) 오호 티맵
        return Arrays.stream(arr)
                .map(n -> n * 2)
                .sum();
    }

    boxed()

    기본 자료형을 그에 해당하는 wrapper 클래스로 변환하는 역할

    Ex : intStream -> Stream<Integer>

    IntStream intStream = IntStream.range(1, 5);
    Stream<Integer> integerStream = intStream.boxed();

    sorted()

    기본자료형 배열과 함께 사용하면 인자 없이 사용하여 오름차순으로만 정렬할 수 있다.

    IntStream sorted();

    wrapper 클래스 배열과 함께 사용한다면 Comparator를 구현하여 사용자 정의 오름차순 구현이 가능해진다.

    Stream<T> sorted( Comparator<? super T> comparator )



    ArrayList 객체에 스트림 생성/사용

    ArrayList<String> 을 stream으로 변환해서 출력하기 1

    List<String> list = new ArrayList<>();
    list.add("뽀로로");
    list.add("루피");
    list.add("에디");
    
    Stream<String> stream = list.stream();
    stream.forEach(System.out::println);
    // 뽀로로
    // 루피
    // 에디

    ArrayList<String> 을 stream으로 변환해서 출력하기 2

    List<String> list = new ArrayList<>();
    list.add("뽀로로");
    list.add("루피");
    list.add("에디");
    
    Stream<String> stream = list.stream();
    stream.sorted().forEach(System.out::println);
    // 루피
    // 뽀로로
    // 에디

    map

    private static void print3(List<String> list) {
    	Stream<String> stream = list.stream();
    	stream.map(s -> s.length())
    	        .forEach(System.out::println);
    }
    // 3
    // 2
    // 2

    filter

    private static void print3(List<String> list) {
    	Stream<String> stream = list.stream();
    	stream.filter(s -> s.length() > 2)
    	        .forEach(System.out::println);
    }
    // 뽀로로

    reduce 연산

    reduce를 사용하면 연산 수행에 대한 구현을 할 수 있다. 오, reduce 잘 쓰면 이걸로 왠만한건 전부 해결 할 수 있을지도...!!

    인자로 사용할 수 있는 인터페이스 1

    BiFunction<U, ? super String, U>

    public interface BiFunction<T, U, R> {
        R apply(T, U);
    }

    간단한 사용 예시

    BiFunction<Integer, Integer, String> sum = (a, b) -> String.valueOf(a + b);
    System.out.println( sum.apply(2, 3) );  // 5

    인자로 사용할 수 있는 인터페이스 2

    BinaryOperator<String> BiFunction<T, T, T>의 특수한 형태로 만들어진 인터페이스이다.

    public interface BinaryOperator<T> extends BiFunction<T, T, T> {
        @Override
        T apply(T, T);
    }

    간단한 사용 예시

    BinaryOperator<String> bo = (s, s2) -> s.length() >= s2.length() ? s : s2;
    System.out.println( bo.apply(str1, str2) );

    reduce 간단 사용 예시

    public class reduceStudy {
        public static void main(String[] args) {
            String[] arr = {"안녕하세요!", "Hi!", "Hi~~~", "Hello!", "Hello~~~", "안녕하세요 ^^"};
    
            System.out.println( print(arr) );
        }
    
        private static String print(String[] arr) {
            return Arrays
                    .stream( arr )
                    .reduce(
                            "초기값초기값초기값초기값초기값초기값초기값초기값초기값",
                            (s1, s2) -> s1.length() >= s2.length() ? s1 : s2
                    );
        }
    }

    print()에서 사용 된 reduce()를 보면 , 매개변수를 2개 넘겨주고 있는데, 첫번째로 넘어간 값은 U u 타입이고, 두번째로 넘어간 값은 T BinaryOperator<T> 타입이다.

    두번째로 넘어간 값이 첫번째로 넘어간 값 보다 길이가 길어야 반환된다.

    첫번째로 넘어간 값 보다 길이가 짧으면 첫번째 인자가 반환된다.

    그래서 위의 예시 결과는 초기값초기값초기값초기값초기값초기값초기값초기값초기값 이다.

    'Language > Java' 카테고리의 다른 글

    Java : stream 내에서 exception 다루기 (링크)  (0) 2023.12.13
    UUID.randomUUID()  (0) 2023.12.01
    함수형 인터페이스  (0) 2023.12.01
    enum 클래스  (0) 2023.11.30
    Optional  (0) 2023.11.30

Designed by Tistory / Custom by 얼거스