[JAVA] 스트림(Stream)

2022. 9. 18. 22:10

스트림

자바 8 전까지 많은 데이터를 다룰 때 컬렉션이나 배열에 데이터를 담고 for문, Iterator를 사용했다.

이러한 방법들은 코드의 가독성이 떨어지고 데이터 소스들을 각각 다른 방법으로 다뤄야 한다. 

 

다양한 데이터 소스들을 스트림으로 만들기만 하면 표준화된 방법으로 작업을 할 수 있게 해주는 것이 스트림이다.

 

스트림의 특징

1. 스트림은 읽기 전용이다. (원본 데이터를 변경하지 않는다.)

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(4, 1, 5, 3, 2); 
        List<Integer> sorted = list.stream().sorted().collect(Collectors.toList());
        
        System.out.println(list); //[4, 1, 5, 3, 2]
        System.out.println(sorted); //[1, 2, 3, 4, 5]
    }
}

2. 생성한 스트림은 일회성이다.(한 번 사용하면 재사용이 불가능하다.)

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(4, 1, 5, 3, 2);
        IntStream stream = list.stream().mapToInt(Integer::intValue);
        stream.forEach(i -> System.out.print(i)); //12345
        System.out.println();
        
        int max = stream.max().getAsInt(); // 에러!!
        System.out.println(max);
    }
}

스트림은 최종 연산을 수행하면 스트림이 닫혀서 다시 사용할 수 없다. 사용하기 위해서는 데이터 소스를  이용해서 스트림을 다시 만들어야 한다.

 

3. 스트림은 반복 작업을 내부적으로 처리한다.

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

반복문을 스트림 내부적으로 숨겨 놓고 처리하기 때문에 코드의 가독성이 좋아진다.


스트림 생성

스트림 생성  - 컬렉션

List<Integer> list = List.of(1,2,3,4,5);
Stream<Integer> stream = list.stream();

collection 인터페이스의 stream 사용

 

스트림 생성 - 배열

객체 배열

Stream<T> Stream.of(T...values) //가변인자
Stream<T> Arrays.stream(T[])
Stream<T> Arrays.stream(T[]array,int startinclusive, int endExclusive)

Stream<String> stream = Stream.of("a", "b", "c");
Stream<String> stream1 = Arrays.stream(new String[]{"a", "b", "c"});
Stream<String> stream2 = Arrays.stream(new String[]{"a", "b", "c"}, 0, 2);

Stream의 of와 Arrays의 stream 메서드로 생성

 

기본형 배열

IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
IntStream intStream1 = IntStream.of(new int[]{1, 2, 3, 4, 5});
IntStream intStream2 = Arrays.stream(new int[]{1, 2, 3, 4, 5});
IntStream intStream3 = Arrays.stream(new int[]{1, 2, 3, 4, 5}, 0, 3);

IntStream 말고도 LongStream, DoubleStream이 있다.


스트림의 연산

스트림은 스트림생성 - 중간연산 - 중간연산 - ... - 최종 연산의 순서로 이루어진다.

스트림을 생성하고 중간 연산은 n번 최종 연산은 1번 수행한다.

 

중간 연산의 결과는 새로운 스트림을 반환하고 최종 연산은 스트림을 반환하지 않는다.

Stream<Integer> stream = Stream.of(1, 12, 3, 4, 6, 10, 12, 27, 40); //생성
Stream<Integer> filterStream = stream.filter(i -> i % 2 == 0); //중간 연산
Stream<Integer> distinctStream = filterStream.distinct(); //중간 연산
Stream<Integer> sortedStream = distinctStream.sorted(); //중간 연산
sortedStream.forEach(i -> System.out.print(i)); //최종 연산

중간 연산

중간 연산의 결과는 스트림으로 반환한다. 반환된 결과로 다른 연산을 할 수 있기 때문에 반복적으로 연산이 가능하다.

 

- skip()

n개를 건너뛴다. 

public class StreamTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
        stream.skip(3).forEach(i -> System.out.print(i)); //456
    }
}

 

- limit()

스트림의 앞에서부터 n개까지만 자른다.

public class StreamTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
        stream.limit(3).forEach(i -> System.out.print(i)); //123
    }
}

 

- filter()

스트림에서 조건에 맞는 데이터만 걸러낸다.

public class StreamTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
        stream.filter(i -> i % 2 == 0).forEach(i -> System.out.print(i)); //246
    }
}

 

- distinct()

중복을 제거한다.

public class StreamTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 1, 2, 2, 3, 3);
        stream.distinct().forEach(i -> System.out.print(i)); //123
    }
}

 

- sorted()

스트림을 정렬한다.

public class StreamTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(2,3,1,4,7,6,5);
        stream.sorted(Comparator.reverseOrder())
                .forEach(i -> System.out.print(i)); //7654321

        System.out.println();

        stream = Stream.of(2,3,1,4,7,6,5);
        stream.sorted().forEach(i -> System.out.print(i)); //1234567
    }
}

 

- map()

스트림의 요소를 변환한다.

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = List.of(1, 2, 3, 4, 5);
        list.stream()
                .map(i -> i * 2)
                .forEach(i -> System.out.print(i)); //246810
    }
}

 

- peek()

스트림의 요소를 탐색한다.

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = List.of(1, 2, 3, 4, 5);
        list.stream()
                .peek(i -> System.out.print(i))
                .map(i -> i * 2)
                .peek(i -> System.out.print(i))
                .forEach((i) ->System.out.println());
    }
}


/*
12
24
36
48
510
*/

 

- flatMap()

스트림의 스트림을 스트림으로 변환

Stream<Integer[]> stream = Stream.of(new Integer[]{1, 2, 3},
                                     new Integer[]{4, 5, 6});

Stream<Stream<Integer>> streamStream = stream.map(arr -> Arrays.stream(arr));

Stream<Integer []>일 때 각 arr를 스트림으로 변환하면 스트림 안에 스트림(Stream<Stream<Integer>>으로 변환된다.

Stream<Integer []>를 Stream<Integer>로 바꾸고 싶을 때 flatMap을 사용하면 된다.

 

Stream<Integer[]> stream = Stream.of(new Integer[]{1, 2, 3},
                                     new Integer[]{4, 5, 6});

Stream<Integer> integerStream = stream.flatMap(arr -> Arrays.stream(arr));

 

최종 연산

최종 연산의 결과는 스트림이 아닌 결과를 반환한다. 스트림으로 반환하지 않기 때문에 한 번만 수행할 수 있다.

 

- forEach()

각각의 데이터에 작업을 수행한다. 

String[] arr = {
        "Hello World",
        "Java World"
};

Stream<String> stream = Arrays.stream(arr);
stream.flatMap(s -> Stream.of(s.split(" ")))
        .forEach(s -> System.out.println(s));
        
/*
Hello
World
Java
World
*/

 

- allMatch(), anyMatch(), noneMatch()

조건에 일치하는지 아닌지 boolean 타입으로 반환한다.

Stream<String> stream = Stream.of("Hello", "World", "Java", "World");

boolean reseult = stream.map(s -> s.toLowerCase())
        .anyMatch(s -> s.startsWith("h"));
System.out.println(reseult); //true

stream = Stream.of("Hello", "World", "Java", "World");
boolean result2 = stream.map(s -> s.toLowerCase())
        .noneMatch(s -> s.startsWith("k"));
System.out.println(result2); //true

stream = Stream.of("Hello", "World", "Java", "World");
boolean result3 = stream.map(s -> s.toLowerCase())
        .allMatch(s -> s.startsWith("h"));
System.out.println(result3); //false

 

- reduce()

요소를 하나씩 줄여가면서 연산을 수행한다. 

초기값을 지정할 수 있다.

Stream<Integer> stream = Stream.of(1,2,3,4,5);
Integer sum = stream.reduce(0, (a, b) -> a + b);
System.out.println(sum); //15

stream = Stream.of(1,2,3,4,5);
Integer sum2 = stream.reduce(10, (a, b) -> a + b);
System.out.println(sum2); //25

 

- collect()

Collector 인터페이스를 구현해 놓은 Collectors를 이용하는 최종 연산

 

스트림을 컬렉션, 배열로 변환할 할 수 있다.

class Student {
    String name;
    int ban;

    public Student(String name, int ban) {
        this.name = name;
        this.ban = ban;
    }

    public String getName() {
        return name;
    }

    public int getBan() {
        return ban;
    }
}
ArrayList<Student> arr = new ArrayList<>(){{
    add(new Student("Kim",1));
    add(new Student("Lee",2));
    add(new Student("Park",3));
}};

List<String> collect = arr.stream().map(student -> student.getName())
        .collect(Collectors.toList());
System.out.println(collect); //[Kim, Lee, Park]
ArrayList<Student> arr = new ArrayList<>(){{
    add(new Student("Kim",1));
    add(new Student("Lee",2));
    add(new Student("Park",3));
}};

String[] objects = arr.stream().map(Student::getName).toArray(String[]::new);
        
for (String object : objects) {
    System.out.println(object);
}

/*
Kim
Lee
Park
*/

 

BELATED ARTICLES

more