[JAVA] 스트림(Stream)
스트림
자바 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
*/
'Programming > Java' 카테고리의 다른 글
[OOP] SOLID 5원칙 (0) | 2022.10.18 |
---|---|
[JAVA] 람다(Lambda) (0) | 2022.09.15 |
[JAVA] 제네릭(Generic) (0) | 2022.09.15 |
[JAVA] 예외와 예외 처리(Exception handling) (0) | 2022.09.13 |
[JAVA] 객체지향 프로그래밍(OOP) - 추상화와 추상 클래스 (0) | 2022.09.08 |