JAVA8 스트림 예제로 익숙해지기

반응형
728x90
반응형

소스코드 : https://github.com/westssun/moderninjava8/tree/master/src/main/java/ModernInJava8/ch4_5_6_stream

 

GitHub - westssun/moderninjava8: [BOOK] 모던 인 자바8

[BOOK] 모던 인 자바8. Contribute to westssun/moderninjava8 development by creating an account on GitHub.

github.com

 

예제 실행을 위한 DTO

@Data
public class SampleDto {
    private int idx;
    private String name;
    private String gender;
}

 

1) 스트림 기본

/**
 * 기존 Java7 코드와 Java8 코드를 비교해보자.
 */
public class BasicStream {
    public static void main(String[] args) {
        java7Code();
        java8Code();
    }

    /**
     * java7
     */
    static void java7Code() {
        /* java7 */
        List<SampleDto> list = new ArrayList<>();
        List<SampleDto> sampleDtoList = new ArrayList<>();

        for (SampleDto sampleDto : sampleDtoList) {
            if (sampleDto.getIdx() < 10) {
                list.add(sampleDto);
            }
        }

        /* 익명클래스 */
        Collections.sort(list, new Comparator<SampleDto>() {
            @Override
            public int compare(SampleDto o1, SampleDto o2) {
                return Integer.compare(o1.getIdx(), o2.getIdx());
            }
        });

        /* 정렬된 리스트 중 sampleDto의 getName 셋팅 */
        List<String> stringList = new ArrayList<>();
        for (SampleDto sampleDto : list) {
            stringList.add(sampleDto.getName());
        }
    }

    /**
     * java8
     */
    static void java8Code() {
        /* java8 */
        List<SampleDto> sampleDtoList = new ArrayList<>();

        List<String> list = sampleDtoList.stream()
                /** 람다를 인수로 받아, 스트림에서 특정 요소를 제외시킨다. 아래는 idx가 10 이상인 데이터를 선택한다. */
                .filter(d -> d.getIdx() < 10) // idx가 10보다 작은 데이터 선택
                .sorted(Comparator.comparing(SampleDto::getIdx)) // idx 순서로 정렬
                /** 람다를 이용해서 한 요소를 다른 요소로 변환하거나 정보를 추출한다. */
                .map(SampleDto::getName) // 이름 추출
                //.limit(3) // 선착순 3개만 선택
                .collect(Collectors.toList()); // 리스트로 저장

        /* 병렬 */
        List<String> parallelList = sampleDtoList.parallelStream()
                            .filter(d -> d.getIdx() < 10) // idx가 10보다 작은 데이터 선택
                            .sorted(Comparator.comparing(SampleDto::getIdx)) // idx 순서로 정렬
                            .map(SampleDto::getName) // 이름 추출
                            .collect(Collectors.toList()); // 리스트로 저장
    }

    /**
     * 스트림은 단 한번만 소비 가능하다.
     */
    static void impossibleNewStream() {
        List<String> title = Arrays.asList("A", "B", "C");
        Stream<String> s = title.stream();

        s.forEach(System.out::println); // A, B, C 출력
        /** java.lang.IllegalStateException:스트림이 이미 소비되었거나 닫힘 에러 발생 */
        s.forEach(System.out::println);
    }
}

 

 

2) Stream filter

public class StreamFilter {
    public static void main(String[] args) {

    }

    /**
     * 중복 필터링
     */
    static void distinct() {
        /* 내부반복 */
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);

        numbers.stream()
                .filter(i -> i % 2 == 0)
                .distinct() /* 중복 필터링 */
                .forEach(System.out::println);
    }
}

 

 

3) Stream 외부반복/내부반복

/**
 * 외부반복/내부반복
 외부반복 : 사용자가 직접 요소를 반복 (for-each 등)
 내부반복 : 스트림 라이브러리 -반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장해준다
 */
public class StreamIteration {
    public static void main(String[] args) {
        forEachCode();
    }

    /**
     * 내부반복/외부반복 예제
     */
    static void forEachCode() {
        /* 내부반복 */
        List<String> names = new ArrayList<>();
        List<SampleDto> sampleDtoList = new ArrayList<>();

        for (SampleDto sampleDto : sampleDtoList) {
            names.add(sampleDto.getName());
        }

        /* 외부반복 */
        List<String> streamNames =  sampleDtoList.stream()
                                        .map(SampleDto::getName)
                                        .collect(Collectors.toList());
    }
}

 

 

4) Stream Mapping

public class StreamMapping {
    public static void main(String[] args) {

    }

    /**
     * Map
     */
    static void useMap() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * 1) Map
         * 함수를 인수로 받는 메서드
         * 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.
         * (map은 '고친다'라는 의미 보다는 '새로운 버전을 만든다' 라는 개념에 가까우므로 매핑이라는 단어를 사용한다)
         */
        List<Integer> list = sampleDtoList.stream()
                                            .map(SampleDto::getIdx) /* idx만의 새로운 버전 생성 */
                                            .collect(Collectors.toList());

        /**
         * idx(Integer -> Long)
         * map 메서드의 연결
         */
        List<Long> list2 = sampleDtoList.stream()
                                            .map(SampleDto::getIdx) /* idx만의 새로운 버전 생성 */
                                            .map(Integer::longValue)
                                            .collect(Collectors.toList());
    }
}

 

 

5) Stream Match

public class StreamMatch {
    public static void main(String[] args) {

    }

    /**
     * anyMatch
     */
    static void anyMatch() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * 프레디케이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인
         */
        boolean isChekced = sampleDtoList.stream()
                                         .anyMatch(dto -> dto.getIdx() > 10);
    }

    /**
     * allMatch
     */
    static void allMatch() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * 모든 요소가 주어진 프레디케이트와 일치하는지 확인
         */
        boolean isChekced = sampleDtoList.stream()
                                         .allMatch(dto -> dto.getIdx() > 10);
    }

    /**
     * noneMatch
     */
    static void noneMatch() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * 모든 요소가 프레디케이트와 일치하지않는지 확인
         */
        boolean isChekced = sampleDtoList.stream()
                .noneMatch(dto -> dto.getIdx() > 10);
    }

    /**
     * findFirst
     */
    static void useFindFirst() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        /**
         * 첫번째 값을 반환한다
         */
        Optional<Integer> first = numbers.stream()
                                         .map(n -> n * n)
                                         .filter(n -> n % 3 == 0)
                                         .findFirst();
    }
}

 

 

6) 숫자스트림

public class StreamNumberic {
    public static void main(String[] args) {

    }

    static void basic() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * 문제점. 아래 코드에는 박싱 비용이 숨어있다. Integer->int로 언박싱해야한다.
         */
        int idxSum = sampleDtoList.stream()
                                    .map(SampleDto::getIdx)
                                    .reduce(0, Integer::sum);

        /**
         * 아래처럼 sum 메소드를 호출할 수 있다면, 위 언박싱 비용이 없어진다.
         * 하지만 .map을 호출한 이후의 결과물은 Stream<T>를 생성하기 때문에 sum 메서드를 호출할 수 없다.
         * 이를 해결하기 위해 '기본형 특화 스트림'을 제공한다.
         */
        /*
        int idxSum2 = sampleDtoList.stream()
                                    .map(SampleDto::getIdx)
                                    .sum();
        */

        /**
         * 숫자 스트림으로 매핑하기
         */
        int idxSum3 = sampleDtoList.stream()
                                    .mapToInt(SampleDto::getIdx) /* 결과를 int type */
                                    .sum(); /* 만약 스트림이 비어있다면 0 (기본값)을 반환 */
    }
}

 

 

7) 스트림 연산

public class StreamOperation {
    public static void main(String[] args) {
        operation();
    }

    /**
     * 스트림 연산
     */
    static void operation() {
        /* java8 */
        List<SampleDto> sampleDtoList = new ArrayList<>();

        List<String> list = sampleDtoList.stream()
                /** 중간연산 시작 ----------------------- */
                                        .filter(d -> d.getIdx() < 10) // idx가 10보다 작은 데이터 선택
                                        .sorted(Comparator.comparing(SampleDto::getIdx)) // idx 순서로 정렬
                                        .map(SampleDto::getName) // 이름 추출
                                        .limit(3) // 선착순 3개만 선택
                /** 최종연산 시작 ----------------------- */
                                        .collect(Collectors.toList()); // 리스트로 저장
    }

    /**
     * 스트림 중간연산에 코드 넣기
     */
    static void opretion2() {
        /* java8 */
        List<SampleDto> sampleDtoList = new ArrayList<>();

        List<String> list = sampleDtoList.stream()
                /** 중간연산 시작 ----------------------- */
                .filter(d -> {
                    System.out.println(d.getName());
                    return d.getIdx() < 10; // return 변경
                })
                .sorted(Comparator.comparing(SampleDto::getIdx))
                .map(d -> {
                    System.out.println(d.getName());
                    return d.getName(); // return 변경
                })
                .limit(3)
                /** 최종연산 시작 ----------------------- */
                .collect(Collectors.toList());
    }
}

 

 

8) Stream Reduce

public class StreamReduce {
    public static void main(String[] args) {

    }

    /**
     * reduce 연산
     */
    static void useReduce() {
        /* before */
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        int sum = 0;

        for (int x : numbers) {
            sum += x;
        }

        /* java 8 */
        /**
         * reduce
         * identity 의미 = 초기값 : 0
         */
        // int sum2 = numbers.stream().reduce(0, (a, b) -> a + b);
        int sum2 = numbers.stream().reduce(0, Integer::sum);

        /**
         * 만약 초기값을 설정하지 않는다면? Optional 로 받자.
         * 스트림에 아무 요소가 없을 경우 Optional 객체로 감싼 객체를 반환한다
         */
        Optional<Integer> sum3 = numbers.stream().reduce(Integer::sum);

        /**
         * max, min
         */
        int getMax = numbers.stream().reduce(0, Integer::max);
        int getMin = numbers.stream().reduce(0, Integer::min);
    }
}

 

 

9) Stream Slice

public class StreamSlice {
    public static void main(String[] args) {

    }

    /**
     * filter
     */
    static void useFilter() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * 1) filter
         * filter 메서드를 사용하면 전체 스트림을 반복하면서 각 요소에 프레디케이틑 적용하게된다.
         * 만약 idx가 정렬된 리스트일 경우, idx가 10보다 큰 경우부터는 반복 작업을 중단할 수 있다.
         * 이런 경우에는 useTakeWile을 사용해보자. 아래 예제를 보자.
         */
        List<SampleDto> list = sampleDtoList.stream()
                                            .filter(dto -> dto.getIdx() < 10)
                                            .collect(Collectors.toList());

    }

    /**
     * takeWhile
     */
    static void useTakeWile() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * filter 메서드와는 다르게, 조건문을 만족하지 않은 경우 반복 작업을 중단한다.
         */
        List<SampleDto> list = sampleDtoList.stream()
                                            .takeWhile(dto -> dto.getIdx() < 10)
                                            .collect(Collectors.toList());
    }

    /**
     * dropWhile
     */
    static void useDropWhile() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * takeWhile과 정반대의 작업 수행
         * 프레디케이트가 처음으로 거짓이 되는 지점까지 발견된 요소를 제거한다.
         * -> idx가 10보다 큰 요소를 탐색한다.
         */
        List<SampleDto> list = sampleDtoList.stream()
                                            .dropWhile(dto -> dto.getIdx() < 10)
                                            .collect(Collectors.toList());
    }

    /**
     * limit
     */
    static void useLimit() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * limit
         */
        List<SampleDto> list = sampleDtoList.stream()
                                            .filter(dto -> dto.getIdx() < 10)
                                            .limit(3) /* 최대 요소 3개 반환 */
                                            .collect(Collectors.toList());
    }

    /**
     * skip
     */
    static void useSkip() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * skip
         */
        List<SampleDto> list = sampleDtoList.stream()
                .filter(dto -> dto.getIdx() < 10)
                .skip(2) /* 처음 두개의 idx를 건너뛴 후 나머지 idx를 반환한다 */
                .collect(Collectors.toList());
    }
}

 

 

10) Stream flatMap

public class StreamSplitMap {
    public static void main(String[] args) {

    }

    /**
     * split map
     */
    static void splitMap() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "AAAAAAAA", "AA"),
                new SampleDto(2, "BBBBBBBB", "BB")
        );

        /**
         * 결과타입 : List<String[]>
         * split("") 실행 이후 우리가 기대한 결과 타입은 Stream<String>이다.
         */
        List<String[]> list = sampleDtoList.stream()
                .map(dto -> dto.getName().split(""))
                .distinct()
                .collect(Collectors.toList());

        /**
         * 결과타입 : List<Stream<String>>
         * 이마저도 우리가 기대한 Stream<String> 결과타입이 아니다.
         * 문제 해결을 위해, 먼저 각 단어를 개별 문자열로 이루어진 배열로 만든 다음에 각 배열을 별도의 스트림으로 만들어야한다.
         */
        String[] arrayOfWords = {"Goodbye", "World"};
        Stream<String> streamofWords = Arrays.stream(arrayOfWords);

        /* 위 Arrays.stream 메서드 호출 추가 */
        List<Stream<String>> list2 = sampleDtoList.stream()
                .map(dto -> dto.getName().split("")) /* 각 단어를 개별 문자열 배열로 변환 */
                .map(Arrays::stream) /* 각 배열을 별도의 스트림으로 생성 */
                .distinct()
                .collect(Collectors.toList());
    }

    /**
     * 위 이슈 해결: flatMap 사용
     */
    static void userFlatmap() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "AAAAAAAA", "AA"),
                new SampleDto(2, "BBBBBBBB", "BB")
        );

        /* 위 Arrays.stream 메서드 호출 추가 */
        List<String> list3 = sampleDtoList.stream()
                                    .map(dto -> dto.getName().split("")) /* 각 단어를 개별 문자열 배열로 변환 */
                                    .flatMap(Arrays::stream) /* 생성된 스트림을 하나의 스트림으로 평면화 */
                                    .distinct()
                                    .collect(Collectors.toList());
    }
}

 

 

11) IntStream

public class UseIntStream {
    public static void main(String[] args) {

    }

    static void intStream() {
        /* 내부반복 */
        List<SampleDto> sampleDtoList = Arrays.asList(
                new SampleDto(1, "A", "AA"),
                new SampleDto(2, "B", "BB")
        );

        /**
         * IntStream (기본형의 정수값만 만들 수 있는 숫자 스트림)
         */
        IntStream intStream = sampleDtoList.stream()
                                            .mapToInt(SampleDto::getIdx);
        Stream<Integer> integerStream = intStream.boxed(); /* 숫자 스트림을 스트림으로 변환 */

        /**
         * IntStream에서 0이라는 기본값이 나온다면, 혹시나 잘못된 결과를 도출할 수 있다.
         * 스트림에 요소가 없는 상황과 실제 최대값이 0인 경우를 구별해내야한다.
         * 이를 구분하기위해 OptionalInt 스트림을 사용하자.
         */
        OptionalInt maxIdx = sampleDtoList.stream()
                                            .mapToInt(SampleDto::getIdx)
                                            .max();

        /* orElse : 값이 없을 경우 기본 최대값을 명시적으로 설정 */
        int orElseMaxIdx = maxIdx.orElse(1);
    }
}

 

 

12) 스트림 생성

public class newStream {
    public static void main(String[] args) {

    }

    static void newStream() {
        /**
         * 스트림 생성
         */
        Stream<String> stream = Stream.of("A", "b", "C", "d");

        stream.map(String::toUpperCase)
                .forEach(System.out::println);
    }
}

 

 

13) 무한스트림

public class InfiniteStream {
    public static void main(String[] args) {

    }

    /**
     * iterate
     */
    static void useIterate() {
        /**
         * 무한 스트림 생성
         * 종료시점이 명시되어야한다. -> takeWhile 사용
         */
        IntStream.iterate(0, n -> n + 2)
                .takeWhile(n -> n < 100)
                .forEach(System.out::println);
    }

    /**
     * generate
     */
    static void userGeneriate() {
        /**
         * 무한 스트림 생성
         * iterate와 다른점 : 생산된 각 값을 연속적으로 계산하지 않는다.
         * Supplier<T>를 인수로 받아서 새로운 값을 생성한다.
         * 종료시점 명시 -> limit
         */
        Stream.generate(Math::random)
                .limit(5)
                .forEach(System.out::println);
    }
}

 

 

참고 교재 : 모던 인 자바8

 

반응형

Designed by JB FACTORY