스트림(Stream)

김주영·2022년 12월 13일
0

자바 <면접>

목록 보기
5/9
post-thumbnail

썸넬 img ref : https://www.ifourtechnolab.com/blog/a-guide-on-java-stream-api

🌱 스트림(Stream)이란


스트림은 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓았다.

Collection이나 Iterator 같은 인터페이스를 이용해서 컬렉션을 다루는 방식을 표준화 했지만, 각 컬렉션 클래스에는 같은 기능의 메서드들이 중복해서 정의되어 있다.

List 정렬 : Collection.sort()
배열 정렬 : Arrays.sort()

이렇게 데이터 소스마다 다른 방식으로 다루어야하는 문제점을 해결해주는 것이 Stream이다.

// 기존
String[] strArr = {"aaa", "bbb", "ccc"};
List<String> strList = Arrays.asList(strArr);

// 스트림 생성
Stream<String> strStream1 = strList.stream();
Stream<String> strStream2 = Arrays.stream(strArr);

// 스트림 출력
strStream1.sorted().forEach(System.out::println);
strStream2.sorted().forEach(System.out::println);
  • 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐, 변경하지 않음
  • 스트림은 한번 사용하면 닫혀서 다시 사용할 수 없다.
  • 스트림은 작업을 내부 반복으로 처리한다.
    • 조건을 걸어서 중지시켜도 내부적으로는 작업이 끝까지 진행된다.
  • 스트림은 최종 연산이 수행되기 전까지 중간 연산이 수행되지 않는다. 중간 연산을 호출하는 것은 단지 어떤 작업이 수행되어야하는지를 지정해주는 것일 뿐이다.
  • 오토박싱/언박싱의 비효율을 줄이기 위해 데이터 소스의 요소를 기본형으로 다루는 IntStream, LongStream, DoubleStream 이 제공된다.
int sum = strStream.parallelStream()
                     .mapToInt(s -> s.length())
                     .sum();

🌱 숫자 중복 체크


Stream().distinct() 은 stream의 중복을 모두 제거함
Stream().count() 은 stream의 사이즈를 반환함
기존의 크기와 중복을 제거한 stream의 count()가 다르면 중복이 있었음을 의미함

	public static void main(String[] args) {

        List<Integer> numList = Arrays.asList(1,1,2,3,4,5);
        if(numList.size() != numList.stream().distinct().count()){
            System.out.println("중복된 요소가 있습니다! 예외 발생시키기");
        }
    }

ref : https://doing7.tistory.com/145

🌱 정수 합 구하기


public int solution(int n) {
        return Arrays.stream(String.valueOf(n).split(""))
        .mapToInt(Integer::parseInt).sum();
    }

🌱 IntStream.range 활용


🌿 특정 값 인덱스 찾기

📌 특정 값 인덱스 구하기(원시 배열)

public static int find(int[] a, int target) {
    return IntStream.range(0, a.length)
                    .filter(i -> target == a[i])
                    .findFirst()
                    .orElse(-1);    // 타겟을 찾지 못하면 -1 반환
}

📌 특정 값 인덱스 구하기(객체 배열)

public static<T> int find(T[] a, T target) {
    return IntStream.range(0, a.length)
                    .filter(i -> target.equals(a[i]))
                    .findFirst()
                    .orElse(-1);    // 타겟을 찾지 못하면 -1 반환
}

ref : https://www.techiedelight.com/ko/find-index-element-array-java/

🌿 일정 범위 내 필터링된 값들의 합

//n이하 범위에서 짝수 값의 합
public class Solution2 {

    public int solution(int n) {
        return IntStream.rangeClosed(0, n).filter(e -> e % 2 == 0).sum();
    }

    public static void main(String[] args) {
        Solution2 s = new Solution2();
        System.out.println(s.solution(10));
    }

}

🌱 두 배열의 중복값


List도 동일하게 적용

  • 중복 개수 조사
	public int solution(String[] s1, String[] s2) {
        return (int) Arrays.stream(s1)
                .filter(s -> Arrays.stream(s2).anyMatch(Predicate.isEqual(s)))
                .count();
    }
  • 중복되지 않는 개수 조사
 	public int solution(String[] s1, String[] s2) {
        return (int) Arrays.stream(s1)
                .filter(s -> Arrays.stream(s2).noneMatch(Predicate.isEqual(s)))
                .count();
    }

ref : https://shxrecord.tistory.com/262

🌱 배열 <-> 리스트


🌿 배열 -> 리스트

1. asList()

public class ArrayConversion {
    public static void main(String[] args) {
        String[] arr = { "A", "B", "C" };
 
        // 배열 -> List로 변환
        List<String> list = Arrays.asList(arr);
 
        System.out.println(list.get(0)); // "A"
        System.out.println(list.get(1)); // "B"
        System.out.println(list.get(2)); // "C"
    }
}

2. Collectors.toList()

//Stream.of()
public class ArrayConversion {
    public static void main(String[] args) {
        String[] arr = { "A", "B", "C" };
 
        // 배열 -> List로 변환
        List<String> list = Stream.of(arr).collect(Collectors.toList());
 
        System.out.println(list.get(0)); // A
        System.out.println(list.get(1)); // B
        System.out.println(list.get(2)); // C
    }
}
//Arrays.stream()
public void solution(int[] array) {
	List<Integer> list = Arrays.stream(array)
    						   .boxed()
    						   .collect(Collectors.toList());
}

🔎 Arrays.stream() vs Stream.of()

Primitive 타입의 경우 return 타입이 다름
Stream.of() : Stream(int[])
Arrays.stream() : IntStream

🔎 boxed()

boxed() 메소드는 IntStream 같이 원시 타입에 대한 스트림 지원을 클래스 타입(예: IntStream -> Stream<Integer>)으로 전환하여 전용으로 실행 가능한 (예를 들어 int 자체로는 Collection에 못 담기 때문에 Integer 클래스로 변환하여 List<Integer> 로 담기 위해 등) 기능을 수행하기 위해 존재

🌿 리스트 -> 배열

  1. toArray()
public class ArrayListConversion {
    public static void main(String[] args) {
 
        // ArrayList 준비
        ArrayList<String> arrList = new ArrayList<String>();
        arrList.add("A");
        arrList.add("B");
        arrList.add("C");
 
        // ArrayList를 배열로 변환
        String arr[] = arrList.toArray(new String[arrListSize]);
 
        // 변환된 배열 출력
        System.out.println(Arrays.toString(arr));
 
    }
}

ref : https://hianna.tistory.com/551

🌱 최댓값/최솟값


List<Integer> list = Arrays.stream(array).boxed().collect(Collectors.toList());
        max = list.stream().mapToInt(n -> n).max().orElseThrow();
        min = list.stream().mapToInt(n -> n).min().orElseThrow();

🌱 객체 배열 변환


  • 객체[] -> Integer[]

    map()으로 배열을 가공한 후 toArray()로 결과값을 줄 때, 'Integer[]::new'를 한다.

class Main
{
    public static void main(String[] args)
    {
        Object[] objectArray = { 1, 2, 3, 4, 5 };
 
        // 객체 어레이의 요소를 정수 어레이로 복사
        Integer[] integerArray = Arrays.stream(objectArray)
                                    .map(o -> (Integer) o)
                                    .toArray(Integer[]::new);
 
        System.out.println(Arrays.toString(integerArray));
    }
}
  • Integer[] -> int[]

    Wrapper 타입을 Primitive 타입으로 주고 싶을 경우, toArray()로 값을 내줄 때, Integer::intValue를 하면 된다.

public class MyClass {
    public static void main(String args[]) {
        Integer a[] = {1,2,3,4};
        int b[] = Arrays.stream(a).mapToInt(Integer::intValue).toArray(); 

        System.out.println(a.getClass());        // class [Ljava.lang.Integer;
        System.out.println(Arrays.toString(a));  // [1, 2, 3, 4]
        
        System.out.println(b.getClass());        // class [I
        System.out.println(Arrays.toString(b));  // [1, 2, 3, 4]
    }
}

ref:

🌱 Map Grouping


  • Collectors.groupingBy()

    특정 속성 값에 의해서 그룹핑을 짓는 것
    SQL의 Group By와 유사
    결과 값으로 Map<K, V> 반환

  • groupingBy 메서드

  1. classifier (Function< ? super T,? extends K> ): 분류 기준을 나타낸다.
  2. mapFactory (Supplier) : 결과 Map 구성 방식을 변경할 수 있다.
  3. downStream (Collector< ? super T,A,D>): 집계 방식을 변경할 수 있다.
public class Order {
    private String itemName;     //주문아이템 이름
    private Integer amount;      //주문 금액
    private OrderType orderType; //주문 타입
    private String orderBy;      //주문자 이름
}
public enum OrderType {
    PICKUP("포장"),
    DELIVERY("배달"),
    PRESENT("선물하기");

    private final String text;

    OrderType(String text) {
        this.text = text;
    }
}
private static List<Order> orders() {
    return List.of(
        new Order("후라이드 치킨", 17_000, OrderType.DELIVERY, "Andrew"),
        new Order("양념 치킨", 18_000, OrderType.DELIVERY, "Andrew"),
        new Order("피자", 18_000, OrderType.PICKUP, "Andrew"),
        new Order("돈가스", 10_000, OrderType.PICKUP, "Andrew"),
        new Order("모둠초밥", 13_000, OrderType.PRESENT, "Andrew")
    );
}

🌿 단일 키로 groupingBy

OrderType으로 그룹핑
Map의 Key 값이 OrderType으로 됨

Map<OrderType, List<Order>> collect = 
  orders.stream().collect(groupingBy((Order::getOrderType)));

🌿 복합 키로 groupingBy

OrderTuple 이라는 복합 키를 만들고 그룹핑

Map<OrderTuple, List<Order>> collect1 = 
  orders.stream().collect(groupingBy(order -> new OrderTuple(order.getItemName(), order.getOrderType())));

ref : https://umanking.github.io/2021/07/31/java-stream-grouping-by-example/

🌱 2차원 배열 다루기


  • flatMap()

    평탄화라고 부르며, 2차원 배열의 요소들을 모두 꺼내어 1차원 배열로 리턴합니다. 일반 객체를 대상으로 사용

String[][] arrays = new String[][]{ {"a1", "a2"}, {"b1", "b2"}, {"c1", "c2", "c3"} };
Stream<String[]> stream = Arrays.stream(arrays);
Stream<String> stream = stream.flatMap(s -> Arrays.stream(s));
stream.forEach(System.out::println);

/* 결과
a1
a2
b1
b2
c1
c2
c3
*/
//flatMap + filter + map
String[][] arrays = new String[][]{ {"a1", "a2"}, {"b1", "b2"}, {"c1", "c2", "c3"} };
Stream<String[]> stream = Arrays.stream(arrays);
Stream<String> stream = stream.flatMap(s -> Arrays.stream(s));
stream.filter(s-> s.startsWith("a"))
        .map(String::toUpperCase).forEach(System.out::println);

// 결과
// A1
// A2
  • flatMapToInt()

    flatMap과 동일한 기능이며, int[][] 을 받는 경우 사용해야 함

int[][] check = new int[length][length];
Arrays.stream(check)
.flatMapToInt(Arrays::stream)
.filter(elem -> elem == 0)
.count();

ref : https://codechacha.com/ko/stream-filter/

🌱 Stream을 배열로 변환


🌿 List<String> -> String[]

Stream.toArray(Type[]::new)는 Stream을 배열로 변환한다.

public class ConvertStreamToArray {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        String[] result = list.stream()
                .map(String::toUpperCase)
                .toArray(String[]::new);

        for (String s : result) {
            System.out.println(s);
        }
    }
}
//결과
A
B
C

🌿 List<Integer> -> Integer[]

public class ConvertStreamToArray2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        Integer[] result = list.stream()
                .map(x -> x*x)
                .toArray(Integer[]::new);

        for (Integer n : result) {
            System.out.println(n);
        }
    }
}
//결과
1
4
9

🌿 IntStream -> Integer[]

Stream을 Integer[]로 변환하는 경우 boxed()로 int를 Integer로 변환한 뒤에 Array로 변환한다.

public class ConvertStreamToArray3 {
    public static void main(String[] args) {
        int[] num = {1, 2, 3};

        IntStream intStream = Arrays.stream(num);

        Integer[] result = intStream
                .map(x -> x*x)
                .boxed()
                .toArray(Integer[]::new);

        for (Integer n : result) {
            System.out.println(n);
        }
    }
}
//결과
1
4
9

🌿 IntStream -> int[]

Stream을 int[]으로 변환하는 경우, toArray()로 변환시킬 수 있다.

public class ConvertStreamToArray4 {
    public static void main(String[] args) {
        int[] num = {1, 2, 3};

        IntStream intStream = Arrays.stream(num);

        int[] result = intStream
                .map(x -> x*x)
                .toArray();

        for (int n : result) {
            System.out.println(n);
        }
    }
}
//결과
1
4
9

ref : https://codechacha.com/ko/java8-convert-stream-to-array/

🌱 Stream.reduce()


Stream의 요소들을 하나의 데이터로 만드는 작업

연산을 수행하는 부분은 accumulator이며, 직접 구현해서 인자로 전달해야 함

//1~10 합 구하기
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));
//결과
sum: 55
  • 동작 방식

    (x, y) -> (total, 다음 값) 식으로 누적되면서 계산함
    1회 : total(0) + 1 = 1
    2회 : total(1) + 2 = 3
    ...

  • Method reference로 구현

//1~10 합 구하기
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce(Integer::sum);
sum.ifPresent(s -> System.out.println("sum: " + s));
//결과
sum: 55
  • 초기값 설정

    Stream(init, accumulator) 처럼 초기값을 인자로 전달할 수 있음

//1~10 합 구하기
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.reduce(10, (total, n) -> total + n);
System.out.println("sum: " + sum);
//결과
sum: 65
  • reduce 병렬 처리

    Stream.parallel() 은 Stream 연산을 병렬로 처리할 수 있도록 함
    즉, 순차적으로 연산을 수행하지 않고, 여러 개의 연산을 부분적으로 동시에 처리하고 그 작업들을 병합하여 최종적으로 1개의 결과를 냄

//1~10 합 구하기
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.parallel().reduce(0, (total, n) -> total + n);
System.out.println("sum: " + sum);
//결과
sum: 55

(1 + 2) + (3 + 4) ... + (9 + 10) 식으로 두 개씩 묶어서 먼저 계산하고, 그 결과들을 다시 계산함

🔎 빼기의 경우 연산 결과가 기대하는 대로 나오지 않는다. (사전 확인 필요)

  • 병렬 처리에서 순차 처리하도록 하기

    병렬 처리에서 연산 순서에 따라 발생하는 문제 해결

//1~10 순차적 차 구하기(0-1 부터)
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.reduce(0,
        (total, n) -> total - n,
        (total1, total2) -> total1 + total2);
System.out.println("sum: " + sum);
//결과
sum: -55

(total1, total2) -> total1 + total2) 규칙을 추가하여 연산의 결과가 다음 연산에 영향을 주도록 함 (병렬로 처리된 결과들의 관계 명시함)

ref : https://codechacha.com/ko/java8-stream-reduction/

🌱 CollectingAndThen


collecting을 진행한 후 그 결과로 메서드를 하나 더 수행할 수 있도록 함

System.out.println(givenList.stream().collect(collectingAndThen(toList(), Collection::toString)));

givenList를 List로 만든 후 각 요소들을 출력할 수 있는 형태로 만든 후 println() 호출하는 예제
이 때, 리턴 타입은 collectingAndThen()의 뒤의 함수 리턴 값을 따름

return Arrays.stream(nums)
                .boxed()
                .collect(Collectors.collectingAndThen(Collectors.toSet(),
                        phonekemons -> Integer.min(phonekemons.size(), nums.length / 2)));
//set 자료형으로 변환 후, 그 결과로 Integer.min() 함수 수행

ref : https://sabarada.tistory.com/41

0개의 댓글