IT/development

[Java] Java Stream 사용법 정리(jdk 1.8 부터 사용 가능)

알 수 없는 사용자 2022. 11. 23. 07:03
반응형

목차

    image source:https://unsplash.com/s/photos/java

    Java Stream 사용법 정리😃

    Java 1.8부터 지원되기 시작한 stream사용법에 대해 기록한다.

    그동안 stream에 대해 깊게 공부해본 적이 없어서 이 기회에 제대로 공부해 보도록 하겠다.

    앞으로 jdk 1.8을 사용할 경우에는 배열, 컬렉션 다룰 때 for문 도배하기 보단 효율적으로 stream을 사용할 수 있도록 공부할 예정이다.🤗

    예를 들어 int타입 배열의 값을 중복 제거 후 내림차순 정렬해서 list로 반환하려고 한다.

    그러면 스트림을 사용하지 않는 경우는 아래처럼 긴 코드를 입력해야 한다.

    이를 스트림을 이용하면 아래 한줄이면 된다.

    이를 좀 더 풀이하면 아래와 같다.

    System.out.println(Arrays.stream(arr).boxed()	// stream 생성
                    .distinct()	// 중복 제거
                    .sorted(Comparator.reverseOrder())	// 내림차순 정렬
                    .collect(Collectors.toList())	// list로 반환
    );

    stream에 다양한 메소드들이 많기 때문에 메소드들을 잘 붙이면 효율적으로 이용할 수 있다.


    stream은 선언(생성), 가공, 반환 세 부분으로 이루어짐

    코드로 보면 아래와 같다.

    선언(생성)🥰

    배열, 컬렉션(list, set, map..etc) 등을 stream 형태로 만든다.

    String[] arr = {"a", "b", "c", "a", "b", "c", "f"};
    List<String> list = Arrays.asList(arr); 
    
    // 배열 stream 생성
    Stream<String> stream = Arrays.stream(arr);
    // 컬렉션 stream 생성
    Stream<String> cStream = list.stream();
    // 병렬처리 stream 생성
    Stream<String> paStream = list.parallelStream();

    스트림을 선언하고 값을 넣는 위 방법 말고도 아래처럼 직접 바로 사용할 수도 있다.

    Arrays.stream(arr).메소드()...
    list.stream(arr).메소드()...

     

    가공😏

    스트림을 필요한 형태로 가공한다.

    .boxed()

    int, Long, Double 등 숫자 타입의 배열을 stream으로 만들 경우 stream 각종 메소드를 사용하기 위해 사용

    더 찾아보니 IntStream을 Stream으로 변환할 때 boxed()를 사용한다고 한다.

    int 자체로는 Collection에 못 담기에 Integer 클래스로 변환하여 List<Integer> 로 담는 용도 등에 사용

    ex) IntStream -> Stream<Integer>

    컬렉션 스트림에서는 이 메소드를 사용하지 않는다.

    // 배열의 값을 중복 제거 후 내림차순 정렬한 뒤 list로 반환받는 코드(편의 상 한줄로 씀)
    System.out.println(Arrays.stream(arr).boxed().distinct().sorted(Comparator.reverseOrder()).collect(Collectors.toList()));

     

    .count()

    배열, 컬렉션 크기 확인

    // 배열의 크기 확인
    System.out.println(Arrays.stream(arr).count());
    // 컬렉션(list)의 크기 확인
    System.out.println(list.stream().count());

     

    .sorted()

    정렬할 때 사용

    아래 결과를 보면 순서가 정렬되지 않은 int형 배열인 arr과 list의 값이 정렬된 걸 확인 할 수 있다.

    // 배열 정렬
    System.out.println("배열 내림차순 정렬 : " + Arrays.stream(arr).boxed().sorted(Comparator.reverseOrder()).collect(Collectors.toList()));
    // 컬렉션(list)의 크기 확인
    System.out.println("컬렉션 정렬 : " + list.stream().sorted().collect(Collectors.toList()));

     

    .sorted(Comparator.reverseOrder())

    역정렬(내림차순)할 때 사용

    		// 배열 역정렬
    		System.out.println(Arrays.stream(arr).boxed().sorted(Comparator.reverseOrder()).collect(Collectors.toList()));
    		// 컬렉션 역정렬
    		System.out.println(list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList()));

     

    .findFirst()

    스트림의 첫번 째 값을 가져온다.

    // 배열 스트림 첫번 째 값 가져온다.
    System.out.println(Arrays.stream(arr).findFirst().getAsInt());
    // 컬렉션 스트림 첫번 째 값 가져온다.
    System.out.println(list.stream().findFirst().get());

     

    .skip(index)

    값의 인덱스까지 생략하고 그 다음 번지부터 가져온다.

    // 배열 스트림의 첫번 째 인덱스를 제외한 값만 가져온다.
    System.out.println(Arrays.stream(arr).boxed().skip(1).collect(Collectors.toList()));
    // 컬렉션 스트림의 첫번 째 인덱스를 제외한 값만 가져온다.
    System.out.println(list.stream().skip(1).collect(Collectors.toList()));
    // 컬렉션 스트림의 첫번 째 인덱스를 제외한 값만 가져온다.(skip에 0을 넣어보니 변화가 없는 걸 보니 1번지부터 시작인 듯 싶다.)
    System.out.println(list.stream().skip(0).collect(Collectors.toList()));

     

    .skip(배열크기 -1).findFirst()

    스트림의 마지막 인덱스 값 가져오기

    // 배열 스트림의 마지막 값 찾기
    System.out.println(Arrays.stream(arr).skip(arr.length -1).findFirst().getAsInt());
    // 배열 스트림의 마지막에서 두번 째 값 찾기
    System.out.println(Arrays.stream(arr).skip(arr.length -2).findFirst().getAsInt());
    // 컬렉션 스트림의 마지막 값 찾기
    System.out.println(list.stream().skip(list.size() - 1).findFirst().get());
    // 컬렉션 스트림의 마지막에서 두번 째 값 찾기
    System.out.println(list.stream().skip(list.size() - 2).findFirst().get());

     

    .limit(index)

    인덱스까지만 값을 가져온다.

    // 배열 스트림 5번 째 인덱스까지만 값 가져오기
    System.out.println(Arrays.stream(arr).boxed().limit(5).collect(Collectors.toList()));
    // 컬렉션 스트림 5번 째 인덱스까지만 값 가져오기
    System.out.println(list.stream().limit(3).collect(Collectors.toList()));

     

    .distinct()

    중복 생략

    // 배열 스트림의 중복 생략한 값
    System.out.println(Arrays.stream(arr).boxed().distinct().collect(Collectors.toList()));
    // 컬렉션 스트림의 중복 생략한 값
    System.out.println(list.stream().distinct().collect(Collectors.toList()));

     

    .max(데이터타입::compare)

    최대값

    // 배열 스트림의 최대값
    System.out.println(Arrays.stream(arr).boxed().max(Integer::compare).get());
    // 컬렉션 스트림의 최대값
    System.out.println(list.stream().max(Integer::compare).get());

     

    .min(데이터타입::compare)

    최소값

    // 배열 스트림의 최소값
    System.out.println(Arrays.stream(arr).boxed().min(Integer::compare).get());
    // 컬렉션 스트림의 최소값
    System.out.println(list.stream().min(Integer::compare).get());

     

    .average()

    평균

    배열은 바로 사용 가능, 컬렉션의 경우 mapToDouble(Integer::doubleValue)을 한번 거친 다음 사용 가능

    // 배열 스트림의 평균
    System.out.println(Arrays.stream(arr).average().getAsDouble());
    // 컬렉션 스트림의 평균(컬렉션의 경우 아래처럼 .mapToDouble(Integer:doubleValue)을 한번 씌운 다음 .average() 사용 가능
    System.out.println(list.stream().mapToDouble(Integer::doubleValue).average().getAsDouble());

     

    .sum()

    합계

    .average()와 마찬가지로 컬렉션의 경우 mapToInt(Integer::intValue)로 한번 변경 필요

    // 배열 스트림의 합계
    System.out.println(Arrays.stream(arr).sum());
    // 컬렉션 스트림의 합계
    System.out.println(list.stream().mapToInt(Integer::intValue).sum());

    람다(Lambda)를 활용한 stream 메소드😎

    람다에 대한 설명은 아래 글 참조

     

    [Java] Java Lambda 정리(jdk 1.8부터 사용 가능)

    목차 연차는 쌓이는데 공부는 끝이 없고 아직 모르는게 너무 많다. 그래서 노쇠해서 코딩을 못하기 전까지는 꾸준히 부족한 부분을 공부해서 메꿀 생각이다. Java Lambda 정리(java 1.8 이상부터 가능

    yaga.tistory.com

    람다(Lambda)

    람다의 핵심은 (paramter) -> { 수행 코드 }를 통해 메소드를 정의하지 않고도 메소드처럼 사용할 수 있다는 것인데 메소드와 같은 기능이지만 정의하지 않고도 사용하는 것이라고 한다.

    Lambda는 아무 곳에서나 사용이 불가능하기에 대부분은 스트림이나 forEach 등을 사용할 때 일회용으로 사용된다.

    Lambda를 이용한 list, map 출력

    .map((function) -> 수행 코드)

    함수를 paramter값으로 넘기고 코드를 수행한다. 주로 값을 바꿔주거나 더해줄 때 사용한다.

    map은 코드에서 메소드 사용이 불가능한데 이건 .forEach를 사용하면 됨

    //userVo list를 userDto list로 변환
    public List<UserDto> selectUserList() throws Exception{
    
          List<UserVo> userVoList =  userMapper.selectUserList();
          List<UserDto> userDtoList = userVoList.stream().
          map(u -> new UserDto(u.getUserNo(), u.getName(), u.getEmail(), u.getPassword())).collect(Collectors.toList());
            return userDtoList;
       }
    // 배열 스트림에서 map이 1이면 true 아니면 false
    System.out.println(Arrays.stream(arr).boxed().map(val -> val == 1).collect(Collectors.toList()));
    // 컬렉션 스트림에서 map이 1이면 true 아니면 false
    System.out.println(list.stream().map(val -> val ==1).collect(Collectors.toList()));

    // map 값마다 10 더하기
    System.out.println(Arrays.stream(arr).boxed().map(val -> val = val + 10).collect(Collectors.toList()));
    System.out.println(list.stream().map(val -> val = val + 10).collect(Collectors.toList()));

    // map 값 반올림
    System.out.println(Arrays.stream(arr).boxed().map(val -> Math.round(val*10)/10.0).collect(Collectors.toList()));
    System.out.println(list.stream().map(val -> Math.round(val*10)/10.0).collect(Collectors.toList()));

     

    .forEach((parameter) -> 코드)

    각 인덱스의 값을 parameter값으로 넘기고 코드 수행

    값마다 다른 메소드를 수행한다거나 할 때 사용

    // forEach(모든 값마다 입력한 내용 수행)
    Arrays.stream(arr).boxed().forEach(val -> System.out.println("forEach print : " + val));
    System.out.println();
    list.stream().forEach(val -> System.out.println("ForEach print : " + val));

     

    .anyMatch((paramter) -> {코드})

    스트림 중 하나의 값이라도 조건에 맞으면 true

    // 스트림 값 중 하나라도 맞으면 true
    System.out.println(Arrays.stream(arr).anyMatch(val -> val == 100));
    System.out.println(list.stream().anyMatch(val -> val == 40));

     

    .noneMatch((parameter) -> {코드})

    스트림 중 하나의 값도 조건에 맞지 않으면 true

    // 스트림 값 중 하나도 안맞으면 true
    System.out.println(Arrays.stream(arr).noneMatch(val -> val == 10));
    System.out.println(list.stream().noneMatch(val -> val == 99));

     

    .allMatch((parameter) -> {코드})

    스트림의 값이 모두 조건에 맞아야 true

    // 스트림 값 중 모두 조건에 일치하면 true
    System.out.println(Arrays.stream(arr).allMatch(val -> val == 30));
    System.out.println(list.stream().allMatch(val -> val == 1));

     

    .filter((parameter) -> {코드})

    코드에 맞는 값만 가져온다. 자주 쓰이는 함수이다.

    // 조건에 해당되는 값만 가져온다.
    System.out.println(Arrays.stream(arr).boxed().filter(val -> val == 10).collect(Collectors.toList()));
    System.out.println(list.stream().filter(val -> val == 100).collect(Collectors.toList()));

     

    .reduce(값, 데이터타입::sum)

    스트림의 값을 모두 하나로 합칠 때 사용함

    데이터타입과 sum으로 하나로 합친 뒤 마지막에 값을 더해서 반환함

    (String의 경우에는 값, String::concat을 사용)

    // 스트림 값을 모두 하나로 합치고 마지막에 5를 더한다.
    System.out.println(Arrays.stream(arr).reduce(100000, Integer::sum));
    System.out.println(list.stream().reduce(500000, Integer::sum));

    // 스트림을 하나로 합치고 "avenger : "를 붙인다. 
    System.out.println(list2.stream().reduce("avengers : ", String::concat));

    반환😃

    위에서 가공한 값을 원하는 형태로 가져온다.

    값이 하나만 있는 경우라면 get(), getAsInt() 등으로 가져옴

    배열로 가져올려면 끝에 .toArray();

    나머지 컬렉션 형태는 .collect(Collectors.toList()); 이런식으로 가져오면 되고

    list가 아닌 다른 걸로 가져오고 싶으면 Collectors.toList()를 toSet(), toMap() 이런식으로 가져오면 됨

    그 외의 반환 방법은 아래처럼 반환 갯수로 반환할 수도 있고 나머지는 아래 반환 예제에 적었다.

    Long cnt = Arrays.stream(arr).boxed().sorted().collect(Collectors.counting());

     

    아래처럼 특정 문자열을 구분자로 붙여서 반환할 수도 있음

    System.out.println(Arrays.stream(strArr).collect(Collectors.joining("|")));

    반환 예제

    package java_test.dev.java;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    // stream 반환 예제
    public class Test1 {
    public static void main(String[] args) {
    int[] arr = {10, 122, 10, 30, 2, 30, 31, 32, 33, 100, 1};
    List<Integer> list = new ArrayList<>();
    list.add(3);
    list.add(4);
    list.add(1);
    list.add(3);
    list.add(4);
    list.add(40);
    list.add(100);
    
    System.out.println(Arrays.stream(arr).boxed().distinct()); // 반환하기 전, 즉 가공만 한 상태
    System.out.println(list.stream().max(Integer::compare)); // 반환하기 전, 즉 가공만 한 상태
    
    int[] arr2 = Arrays.stream(arr).distinct().toArray(); // 배열로 반환
    for (int i : arr2) {System.out.println(i);}
    System.out.println("=============================================================");
    
    List<Integer> list2 = Arrays.stream(arr).boxed().distinct().collect(Collectors.toList());
    list2.forEach(val -> System.out.println(val));
    System.out.println("=============================================================");
    
    int val2 = list.stream().max(Integer::compare).get(); // 최대값 하나 반환
    System.out.println("val2 : " + val2);
    System.out.println("=============================================================");
    long cnt = list.stream().collect(Collectors.counting()); // 해당하는 갯수 반환
    System.out.println("cnt : " + cnt);
    // 문자열 배열 선언
    String[] strArr = {"10", "20", "30"};
    System.out.println("=============================================================");
    // 컬렉션 내 모든 값을 |를 붙여서 반환 | 없이 붙여줄려면 ""로 변경
    System.out.println(Arrays.stream(strArr).collect(Collectors.joining("|")));
    System.out.println("=============================================================");
    
    Double val4 = Arrays.stream(strArr) // Int 형태로 평균값 반환 (배열이 String일 경우)
    .collect(Collectors.averagingInt(val -> Integer.parseInt(val)));	
    Double val5 = Arrays.stream(strArr) // Double 형태로 평균값 반환(배열이 String일 경우)
    .collect(Collectors.averagingDouble(val -> Double.parseDouble(val)));
    Double val6 = Arrays.stream(strArr) // Long 형태로 평균값 반환(배열이 String일 경우)
    .collect(Collectors.averagingLong(val -> Long.parseLong(val)));
    System.out.println("val4 : " + val4); 
    System.out.println("val5 : " + val5);
    System.out.println("val6 : " + val6); // 값 확인
    System.out.println("=============================================================");
    
    String[] getGroupParti = {"hulk", "ironMan", "loki", "thor", "hulk", "hulk", "hulk", "hulk"};
    // 이름, 갯수로 grouping함(오라클에서 group by해서 가져오는 것과 같은 개념)
    Map<String, Long> map = Arrays.stream(getGroupParti)
                        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
    System.out.println("hulk : " + map.get("hulk")); // 결과 : hulk : 5(hulk가 5개니까)
    System.out.println("=============================================================");
    
    // 조건에 맞으면 true, 아니면 false의 list 형태로 담아줌
    Map<Boolean, List<String>> map2 = Arrays.stream(getGroupParti)
        .collect(Collectors.partitioningBy(val -> val == "hulk"));
    System.out.println(map2.get(true));	// 결과 : [hulk,hulk,hulk,hulk,hulk]
    }
    }

     

    결과

    java.util.stream.DistinctOps$1@e9e54c2
    Optional[100]
    10
    122
    30
    2
    31
    32
    33
    100
    1
    =============================================================
    10
    122
    30
    2
    31
    32
    33
    100
    1
    =============================================================
    val2 : 100
    =============================================================
    cnt : 7
    =============================================================
    10|20|30
    =============================================================
    val4 : 20.0
    val5 : 20.0
    val6 : 20.0
    =============================================================
    hulk : 5
    =============================================================
    [hulk, hulk, hulk, hulk, hulk]

     

    마무리🥱


    stream의 장점🤗

    1. 사용하기 편함

    2. 코드가 간결해짐

    3. 가독성이 높아짐


    stream의 단점😑

    1. 디버깅이 힘듬

    2. 재활용 불가능

    3. 속도가 느림


    장단이 있으니 적재적소에 효율적으로 쓰는게 중요할 듯 싶다.

    절대 외울 필요는 없고 이해를 하고 그를 바탕으로 나중에 참조해서 사용하면 된다.

    역시 기억하는 것보다 기록하는게 더 공부가 되는 듯 싶고 실제로 코딩해보고 블로그에 글을 한번 정리하는 것만으로 기존보단 많이 이해가 되었다.

    코딩하고 결과 보고 이해하고 글 적는 시간 토탈 대략 1시간 넘게 걸린 것 같다.😥

    참조 : Wakestand Island, 2020.12.6, https://wakestand.tistory.com/419, https://wakestand.tistory.com/418

     

    자바 스트림(Stream) 예제부터 사용법까지 정리

    자바에서 스트림이라고 하면 대부분 엄청나게 어려운 기술인 줄 알고 시작도 전에 포기하는 경우가 많은데 스트림은 엄청 간단하고 유용한 기술이다 다만 설명하는 사람들이 쉬운 걸 너무 어렵

    wakestand.tistory.com

    반응형