썸넬 img ref : https://www.ifourtechnolab.com/blog/a-guide-on-java-stream-api
스트림은 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓았다.
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);
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("중복된 요소가 있습니다! 예외 발생시키기");
}
}
public int solution(int n) {
return Arrays.stream(String.valueOf(n).split(""))
.mapToInt(Integer::parseInt).sum();
}
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();
}
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> 로 담기 위해 등) 기능을 수행하기 위해 존재
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));
}
}
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:
Collectors.groupingBy()
특정 속성 값에 의해서 그룹핑을 짓는 것
SQL의 Group By와 유사
결과 값으로 Map<K, V> 반환
groupingBy 메서드
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")
);
}
OrderType으로 그룹핑
Map의 Key 값이 OrderType으로 됨
Map<OrderType, List<Order>> collect =
orders.stream().collect(groupingBy((Order::getOrderType)));
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/
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();
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
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
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
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의 요소들을 하나의 데이터로 만드는 작업
연산을 수행하는 부분은 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) 규칙을 추가하여 연산의 결과가 다음 연산에 영향을 주도록 함 (병렬로 처리된 결과들의 관계 명시함)
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() 함수 수행