Stream 이 왜 사용되는가 ?

자바8 에서 추가됬으며 람다를 활용할 수 있는 기술 중 하나입니다.

자바8 이전에는 배열 또는 컬렉션을 다룰려면 for/foreach 문을 사용하는 방법이 었습니다.

 

이 방법은 단점이 있습니다.

 

1) 복잡해질 수록 로직이 섞입니다.

2) 코드의 양이 많아집니다.

3) 루프가 여러번 도는 현상이 발생할 수 있습니다.

 

그래서 이를 보완하고자 등장한 것이 스트림입니다.

 

 

Stream

  • 스트림은 '데이터의 흐름' 입니다.

1) 배열 또는 인스턴스에 함수 여러 개를 조합

2) 원하는 결과에 대해 필터링, 매핑해 가공된 결과를 얻음

 

전체 -> 매핑 -> 필터링1 -> 필터링2 -> 결과 생성 -> 결과물

 

  • 스트림은 람다를 이용할 수 있습니다.

1) 코드의 양을 줄일 수 있음

2) 배열과 컬렉션을 함수형으로 처리 가능

  • 병럴처리 가능

1) 하나의 작업을 둘 이상 잘게 나눠 동시에 진행 가능

2) 즉, 쓰레드를 활용해 많은 요소를 빠르게 처리 가능

 

 

Stream 의 생성

스트림은 배열과 컬렉션 인스턴스를 이용해서 생성할 수 있습니다.

 

// 1. 배열로 스트림 생성
String[] arr = new Stringp[]{"a","b","c"};
Arrays.stream(arr); 

// 2. 컬렉션으로 스트림 생성
List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream(); 

// 3. StreamBuilder 로 생성
Stream<String> streamBuilder = Stream.<String>builder().add("sungjun").add("jun").build(); //[ sungjun, jun ]

// 4. Stream.generate() 로 생성
Stream<String> generatedStream = Stream.generate(()->"gen").limit(5) // [ "gen","gen","gen","gen","gen" ]

 

  • Stream.generate()

제너레이트 메서드를 사용하면 람다로 값을 넣을 수 있습니다.

 

 

Stream.iterate()

iterate 메서드를 이용하면 초기값과 해당 값을 다루는 람다를 이용해 스트림에 들어갈 요소를 만듭니다.

예제에서는 30 이라는 초기값을 기준으로 2씩 증가하는 값들이 들어갑니다.

 

Stream<Integer> iteratedStream = Stream.iterate(30,n->n+2).limit(5);
[ 30,32,34,36,38 ]

 

 

기본 타입형 스트림

제네릭 ( Stream<String> ) 을 사용하지 않고 기본 (int, double, long) 스트림을 생성할 수 있습니다.

IntStream intStream = IntStream.range(1,5) [1,2,3,4] 
// 두번째 인자는 종료지점에 포함 안됩니다.

 

이를 활용한 랜덤 난수 생성

DoubleStream doubles = new Random().doubles(3); // 랜덤 난수 3개 생성

 

 

Stream 의 다양한 예제

 

  • 문자열 스트링을 숫자로 변환
IntStream charStream = "Stream".chars(); // [ 83,116,114,101,97,109 ]

 

  • 파일 스트림

Files 클래스의 lines 메소드는 해당 파일의 각 라인을 스트링 타입의 스트림으로 만들어줍니다.

Stream<String> lineStream = Files.lines(Paths.get("file.txt"), Charset.forName("UTF-8"));

 

  • Stream 연결하기
Stream<String> stream1 = Stream.of("a","b","c");
Stream<String> stream2 = Stream.of("d","e","f");

stream<String> concat = Stream.concat(stream1,stream2);

 

 

가공하기 ( 중간 작업 )

전체 요소 중에서 API 를 활용해 내가 원하는 것만 뽑아내는 과정입니다.

가공 단계를 중간 작업이라고 하는데, 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어 붙여서 작성할 수 있습니다.

 

 

List<String> names = Arrays.asList("sun","mon","jun");

//Filtering
Stream<String> FilteringName = names.stream().filter(name->name.contains("s"); // [sun]

//Mapping
String<String> MappingName = names.strea().map(String::toUpperCase);
  • Filtering

스트림 내에 요소들을 하나씩 평가해서 걸러내는 작업

  • Mapping

스트림 내에 요소들을 특정 값으로 변환해줍니다. 이때 값을 변환하기 위한 람다를 인자로 받습니다.

 

 

 

IntStream.of(10,20,50,40,30).sorted().collect(Collectors.toList());
// [10,20,30,40,50]

IntStream.of(10,20,50,40,30).sorted(Comparator.reverseOrder()).collect(Collectors.toList());
// [50,40,30,20,10]

List<String> lan = Arrays.asList("seoulpower","geoje","busanwon","bew")
lan.stream().sorted(Comparator.comparingInt(String::length)).collect(Collectors.toList());
// [ bew, geoje,busanwon ... ]
lan.stream().sorted(s1,s2->s2.length()-s1.length()).collect(Collectors.toList());
// [ seoulpower, busanwon, ... ]
  • sorted : 스트림 내에 요소를 오름차순으로 정렬
  • collect(Collectors.변환될 타입) : 스트림으로 반환하는 요소를 해당 컬렉션으로 바꿔서 리턴해줍니다.
  • sorted(Comparator.reverseOrder()) : 스트림 내에 요소를 내림차순으로 정렬
  • sorted(Comparator.comparingInt(String::length)) : 스트림 내에 요소를 문자열 길이 기준으로 정렬

 

결과 만들기

가공한 스트림을 가지고 내가 사용할 결과 값을 만들어내는 단계입니다. 따라서 최종 작업입니다.

 

long count = IntStream.of(1,2,3,4,5).count();
long count = IntStream.of(1,2,3,4,5).sum();
long count = IntStream.of(1,2,3,4,5).min();
long count = IntStream.of(1,2,3,4,5).max();

최소, 최대, 합, 평균 등 기본형 타입으로 결과를 만들어 낼 수 있습니다.

 * 이때, [ 평균/최소/최대 ] 의 경우에는 스트림이 비어있는 경우를 표현할 수 없기때문에 Optional 을 이용해 리턴합니다.

 

 

이 외에, 

Reduction, Collecting, Matching, foreach

에 대한 개념이 있지만 현재 쓸 일이 없어서 기억을 못할 것 같으므로 다음에 사용할 일 있을 때

다시 따로 블로깅 하도록 하겠습니다.

 

 

 

 

조건검사

  • findAny, findFirst()

filter() 연산과 함께 사용됩니다. 조건식에 맞는 요소가 있는지 확인할 때 사용됩니다.

반환 타입이 Optional<T> 이며, 스트림의 요소가 없을 때는 비어 있는 Optional 객체를 반환합니다.

 

  • allMatch() : 조건식에 모든 요소가 일치하면 true
  • anyMath() : 어떤 요소 하나라도 조건에 일치하면 true
  • noneMatch() : 조건식에 모든 요소들이 일치하지많으면 true

 

 

 

 

[ 소스코드 예제 ]

현재 제가 진행중인 프로젝트에서 사용된 스트림에 대해서 알아보겠습니다.

 

private static Map<Long, Member> store = new HashMap<>();
public Optional<Member> findByname(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

 

1. store.values () 의 반환 타입

: Collection<Member> 

 즉, values 는 값을 컬렉션 타입으로 반환 해줍니다. 

 

2. .stream()

: 컬렉션으로 반환된 values 를 stream 으로 만들어줍니다.

 

3. .filter( member -> member.getName().equals(name)) 

: store 내부의 member 인자에서 getName() 을 통해서 name 값을 받아옵니다.

 받아온 name 과 findByname 의 인자로 주어진 name 을 비교해줍니다.

 

4. .findAny()

: filter 한 조건식에 맞는 요소가 있으면 해당 요소를 모두 반환해줍니다. ( 첫번째 하나만 하려면 findFirst )

만약 findAny() 를 붙치지 않는다면 Stream() 을 그대로 반환하는 모습을 볼 수 있습니다.

 

 

 

'JAVA' 카테고리의 다른 글

Java의 이중 중괄호 초기화  (0) 2022.08.11
HashSet  (0) 2022.08.11
HashMap  (0) 2022.08.10
백트래킹  (0) 2022.07.27
람다식  (0) 2022.07.18

+ Recent posts