Programming/Java

[Java9 프로그래밍] 16. 스트림 활용과 Optional

빠모스 2020. 3. 30. 22:51
반응형

스트림(Stream)이란?

  • filter(중간연산) : predicate를 인자로 받아서 true인 요소만을 반환 

 

스트림 API의 활용 - 필터링/슬라이싱

  • distinct(중간 연산) : 유일한 값을 반환한다. 

  • limit (중간 연산) : 지정된 숫자만큼 반환한다.

 

=> 처음 3개까지만 뽑아서 새로운 스트림 만들어라.

=> 최종 연산에는 우리가 사용할 데이터 타입으로 리턴해준다.

 

 

스트림 API의 활용 - 매핑

map (중간 연산) : 스트림의 T 객체를 U로 변환. 파라미터로 Function<T, U>를 사용.

=> map은 기존의 스트림에서 엘레먼트의 수는 그대로 유지하되 엘레먼트의 성격이 달라짐. 그 전엔 Dish라는 스트림이었는데 map 연산을 거치면 map의 파라미터로 들어오는 함수가 각각의 엘레먼트에 어플라이됨. 

=> reduce는 최종 연산인데, int와 같은 하나의 최종적인 값을 만들어낸다. 폴딩이라고도 한다. 인접한 엘레먼트 두개를 특정 로직에서 연산을 수행한다. 200 + 50 = 250이고, 이를 폴딩이라 한다. 이를 250과 또 더함. 

 

 

스트림 API의 활용 - 최종 연산

reduce [최종연산] : reduce(init, operator) 또는 reduce(operator) 형태로 사용 

=> 폴딩이라고도 한다. 결과를 누적시켜나간다.

 

 

NullPointException에서 벗어나기

 

  • Null의 가장 큰 문제점은 모든 타입이 Null이 될 수 있다는 것이다.
  • 그리고 Null로 한 의도를 알 수 없다는 것.
  • 데이터가 없어서 Null로 한건지, 데이터 연산이 안되서 그런건지 모름.
  • Null은 굉장히 성가시고 제대로 해결해주지 않으면 프로그래밍 자체가 안정적이지 못하게 됨.
  • 자바8에서 Null을 처리하는 좋은 방식이 도입됨.

=> USB의 버전을 확인하고 싶을 때 computer 안의 getSoundcard() 메소드에 getUSB 메소드의 getVersion 메소드를 호출하게 되는데, 이 넷 중 하나라도 Null이 있다면 전체가 오류가 나면서 NullPointException이 발생한다. 코드 자체가 불안해진다. 

 

 

NullPointException을 예방하기 위해서 

=> null 체크 : 이를 예방하기 위해 항상 computer!=null 처럼, null이 아닌 경우에만 수행하는 조건문을 걸어줌. 하지만 코드가 길어지고 보기 좋지 않음.

 

 

Optional 사용하기

  • Optional은 값이 있거나 또는 없는 경우를 표현하기 위한 클래스

=> NullPointException을 해결하기 위한 새로운 방법. 기존의 타입을 옵셔널로 래핑함. 

=> optional은 함수형 프로그램의 일환.

=> flatMap을 통해 null인지 아닌지를 판단하여 null이 아닐때는 version이 정확하게 출력되지만 null일 경우 “unknown”이 출력되게 됨. 

 

=> 옵셔널로 래핑해서 널인지 아닌지를 잘 판단해서 널일경우 출력이 안되지만 널이 아닐 경우 (ifPresent) 제대로 출력하게 됨. 

 

=> 자바를 포함한 모던 프로그래밍언어는 다 optional같은 null처리하는 함수가 포함됨. 옵셔널로 래핑하는게 가독성도 좋고 나중에 유지보수도 쉬워진다.

 

 

결과 모으기 - collect() (최종 연산자)

스트림을 이용한 작업을 마치고 결과를 자료구조로 모은다.

  • Collectors.toList() - 작업의 결과를 리스트로 모으기
  • Collectors.toSet() - 작업의 결과를 집합으로 모으기

 

실습 코드

package com.acompany.stream;

import org.w3c.dom.ls.LSOutput;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.stream.Stream;

public class StreamDemo {
    public static void main(String[] args) throws IOException { // 예외는 일단 던져준다.
        // Path 클래스를 이용해 파일을 읽어들인다.
        Path path = Paths.get("cities.txt");
        System.out.println(path.toAbsolutePath());

        // Files 클래스로 한줄씩 읽고 스트림으로 생성한다.
        Stream<String> stringStream = Files.lines(path);

        // forEach를 사용해 스트링 스트림을 각 라인별로 출력해보자.
        //stringStream.forEach(System.out::println);

        // 매핑으로 가공해서 출력해보자. Map은 A가 들어가서 B가 나온다.
        Optional<Integer> integer =
                // reduce로 나오는 숫자는 null일수도, 아닐수도 있기 때문에 optional로 체크해줘야 한다.
                stringStream.map(l -> Integer.parseInt(l.split(",")[1].trim())) // 띄어쓰기를 기준으로 분리한 것의 2번? 인덱스. 즉, 인구수만 출력된다
                // 출력되는 문자는 String이다. 이를 숫자로 다시 바꿔주기 위해 우선 문자 앞 공백을 trim()으로 제거하고 Integer.parseInt로 정수화한다.
                .filter(p -> p >= 1_000_000)
                // 인구가 100만 이상인것만으로 필터링한다.
                //.forEach(System.out::println);
                .reduce((a, b) -> a + b);


        System.out.println(integer.get());
        // 앞서 reduce로 구한 인구 합이 출력된다.

        /*
        //// 데이터를 의도적으로 Null로 만들어보자 ////
        integer = Optional.empty();
        System.out.println(integer.get());
        // => "No value present" 라는 에러가 나옴. 그래서 옵셔널을 쓰면 nullpointexception을 피할 수 있음.
        */

        // 위에처럼 get() 메소드말고 옵셔널에서 데이터를 가져올땐 ifPresent 메소드도 많이 쓴다.
        // 만약 데이터가 있으면 출력하라는 뜻.
        //integer.ifPresent(System.out::println);

        // 데이터가 있으면 원래 데이터를, 없으면 0을 리턴해주세요
        int data = integer.orElse(0);
        System.out.println(data);

    }

}
반응형