[CODE-STATES-BE] SEC-1[JAVA] Effective Java Stream

유형찬·2022년 9월 16일
0

Code States

목록 보기
7/21

스트림

개요

스트림은 데이터의 흐름이다.
배열 , 컬렉션의 저장요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이다.

스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐, 변경할 수 없다.

특징

스트림을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있다.

선언형 프로그래밍이란?

컴퓨터 프로그램을 작성하는 방법 중 하나로,
프로그램의 실행 방법을 기술하는 것이 아니라,
프로그램이 해야 할 일을 기술하는 것이다.

즉 , 코드를 작성하면 동작원리를 모르더라도 어떤 결과가 나올지 알 수 있다.
어떻게 하느냐의 방법은 중요하지 않다.

Stream 예제

    
    public class StreamTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("홍길동");
            list.add("김길동");
            list.add("이길동");
            list.add("박길동");
            list.add("최길동");
    
            //스트림 생성
            Stream<String> stream = list.stream();
            //스트림을 이용한 반복
            stream.forEach(s -> System.out.println(s));
        }
    }

내부 반복자 사용

외부 반복자는 개발자가 직접 컬렉션의 요소를 반복해서 처리해야 한다. (for while)
반면에 내부 반복자는 컬렉션의 요소를 반복해서 처리하는 반복자를 내부에 가지고 있다.

이 뜻은 스트림을 이용하면 개발자가 직접 요소를 반복해서 처리할 필요가 없다는 것이다.

중간 연산과 최종 연산

  • 스트림은 중간 연산과 최종 연산으로 구분된다.
  • 중간 연산은 최종 연산이 수행되기 전까지는 중간 연산을 수행하지 않는다.
  • 중간 연산은 여러개를 연결해서 사용할 수 있다.
  • 최종 연산은 단 한번만 수행할 수 있다.
  • 최종 연산이 수행되면 스트림은 더 이상 사용할 수 없다.

스트림의 생성 및 사용

스트림 생성

스트림은 컬렉션, 배열, 정수 범위, 파일, 람다식으로 생성할 수 있다.

    public class StreamTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("홍길동");
            list.add("김길동");
            list.add("이길동");
            list.add("박길동");
            list.add("최길동");
    
            //스트림 생성
            Stream<String> stream = list.stream();
            //스트림을 이용한 반복
            stream.forEach(s -> System.out.println(s));
        }
    }

배열을 이용한 스트림 생성

    public class StreamTest {
        public static void main(String[] args) {
            String[] arr = {"홍길동", "김길동", "이길동", "박길동", "최길동"};
            Stream<String> stream = Arrays.stream(arr);
            stream.forEach(s -> System.out.println(s));
        }
    }

스트림은 주로 컬렉션 및 배열을 통해서 얻지만 정수 범위, 파일, 람다식으로도 생성할 수 있다.

정수 범위를 이용한 스트림 생성

    public class StreamTest {
        public static void main(String[] args) {
            IntStream stream = IntStream.rangeClosed(1, 10);
            stream.forEach(s -> System.out.println(s));
        }
    }

파일을 이용한 스트림 생성

    public class StreamTest {
        public static void main(String[] args) {
            try {
                Stream<String> stream = Files.lines(Paths.get("C:\\Users\\user\\Desktop\\test.txt"), StandardCharsets.UTF_8);
                stream.forEach(s -> System.out.println(s));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

람다식을 이용한 스트림 생성

    public class StreamTest {
        public static void main(String[] args) {
            Stream<String> stream = Stream.of("홍길동", "김길동", "이길동", "박길동", "최길동");
            stream.forEach(s -> System.out.println(s));
        }
    }

중간 연산

중간 연산은 최종 연산이 수행되기 전까지는 중간 연산을 수행하지 않는다.
중간 연산은 여러개를 연결해서 사용할 수 있다.

    public class StreamTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("홍길동");
            list.add("김길동");
            list.add("이길동");
            list.add("박길동");
            list.add("최길동");
    
            Stream<String> stream = list.stream();
            stream.filter(s -> s.startsWith("김"))
                    .forEach(s -> System.out.println(s));
        }
    }

중간 연산의 종류

  • filter(Predicate<T> predicate): 조건에 맞는 요소만 추출
  • distinct(): 중복 요소 제거
  • sorted(): 요소 정렬
  • sorted(Comparator<T> comparator): 요소 정렬
  • peek(Consumer<T> action): 요소를 소비하면서 중간 결과를 확인
  • limit(long maxSize): 요소의 개수 제한
  • skip(long n): 요소 건너뛰기
  • map(Function<T, R> mapper): 요소의 타입 변환
  • flatMap(Function<T, Stream<R>> mapper): 요소의 타입 변환 후 평탄화
  • mapToInt(ToIntFunction<T> mapper): 요소의 타입을 int로 변환
  • mapToLong(ToLongFunction<T> mapper): 요소의 타입을 long으로 변환
  • mapToDouble(ToDoubleFunction<T> mapper): 요소의 타입을 double로 변환
  • mapToObj(IntFunction<R> mapper): 요소의 타입을 참조 타입으로 변환
  • mapToLong(LongFunction<R> mapper): 요소의 타입을 참조 타입으로 변환
  • mapToDouble(DoubleFunction<R> mapper): 요소의 타입을 참조 타입으로 변환
  • boxed(): 요소의 타입을 참조 타입으로 변환

위와 같은 지원 하는 함수들이 있고 중간 연산 을 통해서 다양한
연산을 파이프 라인화 시켜서 연산 할 수 있다.

최종 연산

최종 연산은 스트림을 소모하면서 연산을 수행한다.

최종 연산의 종류

  • forEach(Consumer<T> action): 요소를 소비
  • forEachOrdered(Consumer<T> action): 요소를 소비
  • toArray(): 요소를 배열로 변환
  • toArray(IntFunction<A> generator): 요소를 배열로 변환
  • reduce(BinaryOperator<T> accumulator): 요소를 하나로 합침
  • reduce(T identity, BinaryOperator<T> accumulator): 요소를 하나로 합침
  • reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner): 요소를 하나로 합침
  • collect(Collector<? super T, A, R> collector): 요소를 수집
  • collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner): 요소를 수집
  • min(Comparator<? super T> comparator): 최소값을 찾음
  • max(Comparator<? super T> comparator): 최대값을 찾음
  • count(): 요소의 개수를 센다.
  • anyMatch(Predicate<? super T> predicate): 조건에 맞는 요소가 있는지 확인
  • allMatch(Predicate<? super T> predicate): 모든 요소가 조건에 맞는지 확인
  • noneMatch(Predicate<? super T> predicate): 모든 요소가 조건에 맞지 않는지 확인
  • findFirst(): 첫 번째 요소를 찾음
  • findAny(): 임의의 요소를 찾음
  • iterator(): 요소를 순회하는 반복자를 반환

상당히 지원하는 최종 연산자가 많지만 대표적인 것들만 나열했다.

Match 연산

Match 연산은 조건에 맞는 요소가 있는지 확인하는 연산이다.

public class MatchExample {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("홍길동", "신용권", "김자바", "람다식", "박병렬");

        boolean hasName = list.stream().anyMatch(s -> s.startsWith("신"));
        System.out.println("hasName = " + hasName);

        boolean noName = list.stream().noneMatch(s -> s.startsWith("신"));
        System.out.println("noName = " + noName);
    }
}

실행 결과

hasName = true
noName = false

Reduce 연산

Reduce 연산은 요소를 하나로 합치는 연산이다.

public class ReduceExample {
    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(
                new Student("홍길동", 10),
                new Student("신용권", 20),
                new Student("김자바", 30)
        );

        int sum1 = studentList.stream()
                .mapToInt(Student::getScore)
                .sum();

        int sum2 = studentList.stream()
                .map(Student::getScore)
                .reduce((a, b) -> a + b)
                .get();

        int sum3 = studentList.stream()
                .map(Student::getScore)
                .reduce(0, (a, b) -> a + b);

        System.out.println("sum1 = " + sum1);
        System.out.println("sum2 = " + sum2);
        System.out.println("sum3 = " + sum3);
    }
}

실행 결과

sum1 = 60
sum2 = 60
sum3 = 60
profile
rocoli에요

0개의 댓글