또한 내부 반복자를 사용하기 때문에 병렬처리가 쉽다는 장점이 있다.
자바 8 이전에 배열 또는 컬렉션 다루는 방법은 for, foreach 를 사용하여 엘리먼트들을 꺼내서 다루는 방법이었다. 간단할 경우 상관 없지만 코드 양이 많아질수록 복잡성이 많이 올라가게 되었다.
스트림을 이용하면 배열 또는 컬렉션을 함수 여러 개를 사용해서 결과를 쉽게 얻을 수 있다.
스트림을 사용하면 람다식을 활용해 코드 양도 줄이고 간결하게 표현도 가능하다.
스트림의 또 하나의 장점은 병렬처리가 가능하다는 것이다.
iterator 처럼 한번만 사용되고 사라진다. 필요하면 다시 스트림을 생성해야 한다.스트림은 크게 생성, 가공, 결과 3가지로 구분된다.
배열, 컬렉션 -> 스트림 생성 -> 매핑 -> 필터링 -> 결과
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. Stream에 대해 이해하고 활용할 수 있다. */
/* 설명.
* Arrays.asList(): 매개변수로 요소들을 전달하면 List로 반환
* ArrayList<>(Collection 타입): Collection 타입을 ArrayList 객체로 생성할 때 쓰이는 생성자
* */
List<String> stringList = new ArrayList<>(Arrays.asList("hello", "world", "stream"));
System.out.println("======== foreach");
for(String str: stringList) {
System.out.println(str);
}
System.out.println("======== stream");
stringList.forEach(System.out::println);
stringList.forEach(a -> System.out.println(a));
}
}import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Application2 {
public static void main(String[] args) {
/* 수업목표. 스트림의 병렬처리에 대해 이해할 수 있다. */
List<String> stringList =
new ArrayList<>(Arrays.asList("java", "oracle", "jdbc", "html", "css"));
/* 설명. main 쓰레드에서 스트림을 사용하지 않고 확인 */
System.out.println("========= foreach");
for (String s: stringList) {
System.out.println(s + " : " + Thread.currentThread().getName());
}
/* 설명. main 쓰레드에서 스트림을 사용하고 확인 */
System.out.println("========= normal stream");
stringList.forEach(Application2::print); // stringList.'stream()'.forEach(Application2::print) 에서 stream() 생략
stringList.forEach(s -> System.out.println(s + " : " + Thread.currentThread().getName()));
/* 설명. 병렬 스트림 사용시 쓰레드 확인(속도가 상대적으로 더 몇 배 빠르다. 또한 기본 main쓰레드 외에 다른 쓰레드를 활용한다.) */
System.out.println("========= parallel stream");
stringList.parallelStream().forEach(Application2::print);
}
private static void print(String s) {
System.out.println(s + " : " + Thread.currentThread().getName());
}
}자바에서는 자주 사용하는 배열과 컬렉션 객체에서 stream() 메소드를 지원한다.
이 메소드를 사용하면 스트림이 생성된다. 이 외 다양한 방법으로 스트림을 생성할 수도 있다.
스트림을 생성할 배열을 생성한 후 stream() 사용
컬렉션도 마찬가지로 stream() 를 사용할 수 있다.
비어있는 스트림도 생성할 수 있는데, 요소가 없을 때 사용하면 된다.
빌더를 사용해 직접 값을 넣을 수도 있다.
iterate() 메소드를 사용하여 수열 형태로 스트림을 생성할 수 있다.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. 배열이나 컬렉션은 스트림을 이용할 수 있고 이를 이해해서 활용할 수 있다. */
String[] sArr = new String[]{"java", "oracle", "jdbc"};
/* 필기.
* Arrays.stream(배열): 배열 자료형을 stream 자료형으로 변환
* */
/* 설명. 1. 배열로 스트림 생성 */
Stream<String> strStream1 = Arrays.stream(sArr);
strStream1.forEach(System.out::println);
// strStream1.forEach(s -> System.out.println(s));
System.out.println(); // 구분을 위한 단순 개행
Stream<String> strStream2 = Arrays.stream(sArr, 0, 2);
strStream2.forEach(System.out::println);
System.out.println();
/* 설명. 컬렉션으로 컬렉션 스트림 생성 */
List<String> stringList = Arrays.asList("html", "css", "javascript");
Stream<String> stringStream3 = stringList.stream();
stringStream3.forEach(System.out::println);
System.out.println();
/* 설명. 3.
* Builder를 활용한 스트림 생성
* builder는 static<T>로 되어 있는 메소드이며, 호출시 타입 파라미터를 메소드 호출 방식으로 전달한다.
* */
Stream<String> builderStream = Stream.<String>builder()
.add("홍길동")
.add("유관순")
.add("윤봉길")
.build();
builderStream.forEach(System.out::println);
/* 설명. 4. iterator()를 활용하여 수열 형태의 스트림을 생성 */
Stream<Integer> intStream =Stream.iterate(10, value -> value * 2)
.limit(10);
intStream.forEach(value -> System.out.println(value + " "));
}
}import java.util.Random;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
public class Application2 {
public static void main(String[] args) {
/* 수업목표. 기본 타입 스트림 생성에 대해 이해하고 활용할 수 있다. */
/* 필기.
* reange(시작값, 종료값): 시작값부터 1씩 증가하는 숫자로 종료값 직전까지 범위의 스트림 생성
* reangeClosed(시작값, 종료값): 시작값부터 1씩 증가하는 숫자로 종료값까지 포함한 스트림 생성
* */
IntStream intStream = IntStream.range(5, 10);
intStream.forEach(value -> System.out.println(value + " "));
System.out.println();
LongStream longStream = LongStream.rangeClosed(5, 10);
longStream.forEach(value -> System.out.println(value + " "));
System.out.println();
/* 필기.
* Wrapper 클래스 자료형의 스트림이 필요한 경우 boxing동 가능하다.
* doubles(개수): 난수를 활용한 DoubleStream을 개수만큼 생성하여 반환한다.
* boxed(): 기본 타입 스트림인 XXXStream을 박싱하여 Wrapper 타입의 Stream<XXX>로 반환한다.
* */
Stream<Double> doubleStream = new Random().doubles(5).boxed();
doubleStream.forEach(value -> System.out.println(value + ""));
System.out.println();
/* 설명. 문자열을 split하여 stream으로 생성 */
Stream<String> splitStream = Pattern.compile(", ").splitAsStream("html, css, javascript");
splitStream.forEach(System.out::println);
}
}스트림을 제네릭을 사용하지 않고 기본 타입으로 스트림을 생성할 수 있다. (range() 와 rangeClosed() 는 범위 차이다.)
제네릭을 사용하지 않기 때문에 불필요한 오토박싱도 일어나지 않는다.
필요한 경우에는 boxed() 를 사용해 박싱을 할 수도 있다.
스트림에 있는 데이터에 대해 내가 원하는 결과를 만들기 위해서 중간 처리 작업을 가공이라고 한다.
스트림에서는 데이터를 가공할 수 있는 메소드들을 제공하는데, 해당 메소드들은 Stream을 전달받아 Stream을 반환하므로 연속해서 메소드를 연결할 수 있다. 또한 Stream 가공할 때에 필터-맵(filter-map) 기반 API를 사용하기 때문에 지연연산을 통해 성능 최적화가 가능하다.
| 상세 역할 | 메소드 |
|---|---|
| 필터링 | filter(), distinct() |
| 변환 | map(), flatMap() |
| 제한 | limit(), skip() |
| 정렬 | sorted() |
| 결과 확인 | peek() |
필터(Filter)는 스트림에서 특정 데이터만 걸러내는 메소드이다. 매개변수로 받는 Predicate는 boolean을 리턴하는 함수형 인터페이스이다.
import java.util.stream.IntStream;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. 스트림의 중계연산 중 하나인 filter에 대해 이해하고 사용할 수 있다. */
/* 설명. 필터(filter)는 스트림에서 특정 데이터만 걸러내는 메소드이다. */
IntStream intStream = IntStream.range(0, 10);
intStream.filter(i -> (i % 2) == 0)
.forEach(i -> System.out.print(i + " "));
}
}map() 메소드는 값을 가공할 수 있는 람다식을 매개변수로 받는다.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
스트림에 들어있는 데이터를 특정 람다식을 통해 데이터를 가공하고 새로운 스트림에 담아주는 역할을 한다.
import java.util.stream.IntStream;
public class Application2 {
public static void main(String[] args) {
/* 수업목표. 스트림의 중계연산 중 하나인 map에 대해 이해하고 사용할 수 있다. */
/* 설명. 맵(map)은 스트림에 들어있는 데이터를 람다식으로 가공하고 새로운 스트림에 담아주는 메소드이다. */
IntStream intStream = IntStream.range(1, 10);
intStream.filter(i -> i % 2 == 0) // boolean 반환형으로 람다식 작성(predicate)
.map(i -> i * 5) // 요소를 반환하는 형태로 람다식 작성(operator)
.forEach(result -> System.out.print(result + " ")); // 반환형 없는 람다식 작성(consumer)
}
}flatMap() 은 map() 과 비슷하지만 조금 더 복잡한 작업을 하는데 확인해보자.
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap() 는 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어준다. 이러한 작업을 플래트닝(flattening)이라고한다.
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class Applcation3 {
public static void main(String[] args) {
/* 수업목표. 스트림의 중계연산중 하나인 flatMap에 대해 이해하고 사용할 수 있다. */
/* 필기.
* Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
* flatMap()는 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어 준다.
* 이러한 작업을 플래트닝(flattening)이라고 한다.
* */
List<List<String>> list = Arrays.asList(
Arrays.asList("JAVA", "SPRING", "SPRINGBOOT"),
Arrays.asList("java", "spring", "springboot")
);
System.out.println("list = " + list);
List<String> flatList = list.stream().flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println("flatList = " + flatList);
}
} 
스트림에 있는 데이터들을 정렬할 때는 sorted() 메소드를 사용한다.
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
sorted() 은 인자가 없이도 호출이 가능한데, 인자가 없으면 오름차순으로 자동 정렬된다. 별도의 비교 로직을 구현하고 싶다면 comparator를 인자로 넘겨주면 된다.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Applicationn4 {
public static void main(String[] args) {
/* 수업목표. 스트림의 중계연산 중 하나인 sorted에 대해 이해하고 사용할 수 있다. */
List<Integer> integerList = IntStream.of(5, 10, 99, 2, 1, 35)
.boxed()
.sorted()
.collect(Collectors.toList());
System.out.println("정렬된 Integer List: " + integerList);
}
}가공된 스트림을 통해 이제는 결과를 만들어 내는 작업이 필요하다.
이 과정은 데이터를 필터링하고 가공한 뒤에 출력하기 위해서 진행하는 작업이다.
스트림에서는 다양한 메소드들을 제공하는데, 먼저 최소/최대/총합/평균 등 과 같은 결과를 알아보자.
만약 스트림이 비어 있으면 count 와 sum은 0을 출력할 것이다.
import java.util.OptionalInt;
import java.util.stream.IntStream;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. 스트림의 최종연산 중 하나인 calculating에 대해 이해하고 사용할 수 있다. */
long count = IntStream.range(1, 10).count();
long sum = IntStream.range(1, 10).sum();
System.out.println("count = " + count);
System.out.println("sum = " + sum);
/* 설명. OptionalInt는 결과 없음을 나타내야 하는 명확한 요구가 있는 메소드 반환 형식으로 사용하기 위한 타입이다. */
OptionalInt max = IntStream.range(1, 10).max();
// OptionalInt max = IntStream.range(1, 1).max(); // OptionalInt.empty 형태로 나오고 기본자료형에서 존재하지 않음을 나타내기 위함을 알 수 있다.
OptionalInt min = IntStream.range(1, 10).min();
System.out.println("max = " + max);
System.out.println("min = " + min);
int oddSum = IntStream.range(1, 10)
.filter(i -> i % 2 == 1)
.sum();
System.out.println("oddSum = " + oddSum);
}
}reduce() 라는 메소드는 스트림에 있는 데이터들의 총합을 계산해준다. reduce() 파라미터에 따라 3가지 종류가 있다.
// 1개 (accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator);
// 2개 (identity)
T reduce(T identity, BinaryOperator<T> accumulator);
// 3개 (combiner)
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);public static void main(String[] args) {
// 인자가 1개일 경우
OptionalInt reduceOneParam = IntStream.range(1, 4) // 1, 2, 3
.reduce((a, b) -> {
return Integer.sum(a, b);
});
System.out.println("reduceOneParam = " + reduceOneParam.getAsInt());
// 인자가 2개일 경우
int reduceTwoParam = IntStream.range(1, 4) // 1, 2, 3
.reduce(100, Integer::sum);
System.out.println("reduceTwoParam = " + reduceTwoParam);
// 인자가 3개일 경우
Integer reduceThreeParam = Stream.of(1, 2, 3, 4, 5, 6 ,7 ,8 ,9 ,10)
.reduce(100,
Integer::sum,
(x, y) -> x + y
);
System.out.println("reduceThreeParam = " + reduceThreeParam);
}reduceOneParam = 6
reduceTwoParam = 106
reduceThreeParam = 155collect() 는 Collector 타입을 받아서 처리하는데, 해당 메소드를 통해 컬렉션을 출력으로 받을 수 있다.
collect() 메소드는 Collector 객체에서 제공하는 정적 메소드를 사용할 수 있다.
public class Member {
private String memberId;
private String memberName;
public Member() {
}
public Member(String memberId, String memberName) {
this.memberId = memberId;
this.memberName = memberName;
}
public String getMemberId() {
return memberId;
}
public void setMemberId(String memberId) {
this.memberId = memberId;
}
public String getMemberName() {
return memberName;
}
public void setMemberName(String memberName) {
this.memberName = memberName;
}
@Override
public String toString() {
return "Member{" +
"memberId='" + memberId + '\'' +
", memberName='" + memberName + '\'' +
'}';
}
}
public static void main(String[] args) {
List<Member> memberList = Arrays.asList(
new Member("test01", "testName01"),
new Member("test02", "testName02"),
new Member("test03", "testName03")
);
}
Collectors.toList()
스트림 작업 결과를 리스트로 반환해주는 메소드이다.
public static void main(String[] args) {
List<Member> memberList = Arrays.asList(
new Member("test01", "testName01"),
new Member("test02", "testName02"),
new Member("test03", "testName03")
);
List<String> collectorCollection = memberList.stream()
.map(Member::getMemberName)
.collect(Collectors.toList());
System.out.println("collectorCollection = " + collectorCollection);
}
collectorCollection = [testName01, testName02, testName03]Collectors.joining()
스트림의 작업 결과를 String 타입으로 이어 붙인다.
세개의 인자를 받을 수 있다. 각 인자는 delimiter(구분자), prefix(맨 앞 문자), suffix(맨 뒤 문자) 이다.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Application3 {
public static void main(String[] args) {
/* 설명. joining()는 하나의 요소들을 합쳐서 하나의 문자열로 바꿔주는 메소드이다. */
String str = memberList
.stream()
.map(Member::getMemberName)
.collect(Collectors.joining());
System.out.println("str = " + str);
}
}
str = testName01testName02testName03import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Application3 {
public static void main(String[] args) {
String str2 = memberList
.stream()
.map(Member::getMemberName)
.collect(Collectors.joining(" || ", "**", "**"));
System.out.println("str2 = " + str2);
}
}
str = **testName01 || testName02 || testName03**Matching 은 Predicate 를 인자로 받아 조건을 만족하는 엘리먼트가 있는지 확인하고 boolean 으로 리턴해준다.
boolean anyMatch(Predicate<? super T> predicate); // 하나라도 조건을 만족하는 값이 있는지
boolean allMatch(Predicate<? super T> predicate); // 모든 조건을 만족하는지
boolean noneMatch(Predicate<? super T> predicate); // 모든 조건을 만족하지 않는지
public static void main(String[] args) {
List<String> stringList = Arrays.asList("Java", "Spring", "SpringBoot");
boolean anyMatch = stringList.stream()
.anyMatch(str -> str.contains("p"));
boolean allMatch = stringList.stream()
.allMatch(str -> str.length() > 4);
boolean noneMatch = stringList.stream()
.noneMatch(str -> str.contains("c"));
System.out.println("anyMatch = " + anyMatch);
System.out.println("allMatch = " + allMatch);
System.out.println("noneMatch = " + noneMatch);
}
anyMatch = true
allMatch = false
noneMatch = true문자열인지 확인: true
문자열인지 확인: false