스트림의 이해와 스트림의 생성
스트림 이해
“데이터의 흐름을 생성 할 수 있으며, 이러한 데이터 흐름을 가리켜 스트림 이라고 한다”
“배열 또는 컬랙션 인스턴스에 저장된 데이터를 꺼내서 파이프에 흘려보낸다.”
파이프에 데이터의 흐름을 가리켜 스트림
중간연산 마지막이 아닌 위치에서 진행이 되어야 하는 연산
최종연산 마지막에 진행이 되어야 하는 연산
원하는 기준으로 데이터를 필터링하고 가공된 결과를 얻을 수 있다.
Stream 첫번째 예제
배열에 저장된 데이터를 대상으로 스트림을 생성, 생성된 스트림을 두 개 파이프를 통과시켜 얻은 결과
import java.util.Arrays;
import java.util.stream.IntStream;
public class Main1 {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(arr);
IntStream stream1 = stream.filter(n -> n % 2 == 1);
int sum = stream1.sum();
System.out.println(sum);
int[] arr2 = {1, 2, 3, 4, 5};
int sum1 = Arrays.stream(arr2)
.filter(n -> n % 2 ==1)
.sum();
System.out.println(sum1);
}
}
IntStream stream = Arrays.stream(arr);
arr에 저장된 데이터를 대상으로 스트림 생성
Arrays 클래스에는 stream이라는 이름의 메소드가 다수 정의. 그리고 이 메소드는 배열을 대상으로 스트림을 생성할 때는 호출하는 메소드이다. 배열 arr에 저장된 데이터 스트림은 데이터의 복사본 이라고 생각하면 된다. 그리고 filter라는 파이프에 통과시키는 방법을 보여준다.
int sum1 = Arrays.stream(arr2)
.filter(n -> n % 2 ==1)
.sum();
System.out.println(sum1);
홀수 인 값을 더해준다.
Stream의 특성
스트림의 생성과 생성된 스트림을 대상으로 하는 연산
import java.util.Arrays;
import java.util.stream.Stream;
public class Main2 {
public static void main(String[] args) {
String[] name = {"1", "2", "3"};
Stream stm = Arrays.stream(name);
stm.forEach(s -> System.out.println(s));
Arrays.stream(name)
.forEach(s -> System.out.println(s));
}
}
두 메소드의 반환형을 IntStream이다.
filter와 sum은 IntStream의 인스턴스 메소드이다. 그리고 스트림 연산은 효율과 성능을 고려해서 lazy 처리 방식으로 동작. sum이 호출되기 전까지 filter 호출 결과는 스트림에 반영되지 않는다. 따라서 최종연산을 잠금벨브 역할을 한다고 보면 된다.
스트림 배열 생성
import java.util.Arrays;
public class Main3 {
public static void main(String[] args) {
double[] ds = {1.1, 2.2, 3.3, 4.4};
Arrays.stream(ds)
.forEach(d -> System.out.println(d));
System.out.println();
Arrays.stream(ds, 1 ,4)
.forEach(d -> System.out.println(d));
System.out.println();
}
}
public static Stream stream (T [] array) // array 클래스에 정의
forEach의 경우 매개변수형이 Consumer이니 추상메소드 구현에 해당하는 람다식을 인자로 전달. accept 메소드를 호출
스트림 생성하기: 컬랙션 인스턴스
import java.util.ArrayList;
import java.util.List;
public class Main4 {
public static void main(String[] args) {
List lst = new ArrayList<>();
lst.stream()
.forEach(s -> System.out.println(s));
System.out.println();
}
}
인스턴스 대상으로도 stream 메소드를 호출
필터링 & 맵핑
필터링
데이터중 일부를 조건에 따라 걸러내야 하는 행위
Stream filter (Predicate <? super T> predicate) 이는 매개변수 형이 Predicate. 그러면 filter 메소드는 내부적으로 스트림 데이터를 하나씩 인자로 전달하면서 test를 호출. 그렇게 되면 true를 반환하면서 해당 데이터는 스트림에 남는다.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main5 {
public static void main(String[] args) {
int[] ar = {1,2,3,4,5};
Arrays.stream(ar)
.filter(n -> n% 2 ==0)
.forEach(n -> System.out.println(n));
System.out.println();
List<String> lst = Arrays.asList("Toy", "Robot", "Car");
lst.stream()
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
System.out.println();
}
}
Mapping
import java.util.Arrays;
import java.util.List;
public class Main6 {
public static void main(String[] args) {
List lst = Arrays.asList("Box", "Robot", "Car");
lst.stream()
.map(s -> s.length())
.forEach(n -> System.out.println(n));
System.out.println();
}
}
문자열 스트림을 숫자 스트림으로 맵핑하였고, 맵핑의 기준은 문자열의 길이. 맵핑을 진행하면, 스트림의 데이터형이 달라지는 것이 특징. 위 메소드의 매개변수 형이 function이다. 따라서 메소드의 구현이 해당 람다식을 인자로 전달. 그러면 map은 내부적으로 스트림의 데이터를 하나씩 인자로 전달 -> apply 메소드를 호출. 그 결과로 반환되는 값을 모아 새로운 스트림을 생성.
<문제>
import java.util.Arrays;
import java.util.List;
class Box{
private T ob;
public Box(T o){
ob = o;
}
public T get(){
return ob;
}
}
public class Main7 {
public static void main(String[] args) {
List ls = Arrays.asList(new Box<>("Robot"), new Box<>("Toy"));
ls.stream()
.map(s -> s.get())
.forEach(s -> System.out.println(s));
}
}
map의 인자로 다음 메소드에 대한 람다식을 전달 하기 때문에 정수의 반환 과정에서 오토박싱이 진행된다.
R apply ( T t )
그래서 자바에서 기본 자료형의 값을 반환하는 경우를 고려하여 다음 맵핑 관련 메소드들도 제공.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main8 {
public static void main(String[] args) {
List lst = Arrays.asList("Box", "Toy", "Robot");
lst.stream()
.mapToInt(s -> s.length())
.forEach(s -> System.out.println(s));
System.out.println();
}
}
Mapping
필터링 후 맵핑
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class ToyPriceInfo{
private String model1;
private int price;
public ToyPriceInfo(String model1, int price) {
this.model1 = model1;
this.price = price;
}
public String getModel1() {
return model1;
}
public int getPrice() {
return price;
}
}
public class Main9 {
public static void main(String[] args) {
List lst = new ArrayList<>();
lst.add(new ToyPriceInfo("Gun", 2000));
lst.add(new ToyPriceInfo("ROBOT", 3000));
lst.add(new ToyPriceInfo("Car", 1000));
int sum = lst.stream()
.filter(p -> p.getPrice() < 5000)
.mapToInt(x -> x.getPrice())
.sum();
System.out.println(sum);
lst.stream()
.filter(t -> t.getModel1().length() > 10)
.map(t -> t.getModel1())
.forEach(s -> System.out.println(s));
}
}
int sum = lst.stream()
.filter(p -> p.getPrice() < 5000)
.mapToInt(x -> x.getPrice())
.sum();
System.out.println(sum);
필터링 후 필터
.mapToInt(x -> x.getPrice())
.sum();
인스턴스에 저장되어 있는 가격 정보를 꺼내서 int 형 스트림 생성.
리덕션, 병렬스트림
리덕션과 reduce
리덕션은 데이터를 축소하는 연산. sum 또한 리덕션 연산에 해당된다. 다수의 데이터를 합이라는 하나의 데이터만 남겼으니 sum 또한 리덕션 연산이다.
T reduce(T identity, BinaryOperator accumulator) // stream에 존재
이 메소드는 다른 리덕션 연산에 비해 활용도 높다. reduce는 전달하는 람다식에 의해 연산 내용이 결정된다.
reduct 호출시 메소드 apply에 대한 람다식을 인자로 전달. 그러면 reduce 내부는 apply를 호출하면서 스트림에 저장된 데이터를 줄여나간다.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BinaryOperator;
public class Main10 {
public static void main(String[] args) {
List lst = Arrays.asList("Box", "simpl ","Complex", "Robot");
BinaryOperator<String> c1 = (s1, s2) -> {
if (s1.length() > s2.length())
return s1;
else
return s2;
};
String str = lst.stream().reduce("", c1);
System.out.println(str);
String str1 = lst.parallelStream()
.reduce("", c1);
System.out.println("str1 = " + str1);
}
}
BinaryOperator c1 = (s1, s2) -> {
if (s1.length() > s2.length())
return s1;
else
return s2;
};
인자로 두개의 문자열을 전달, 길이를 비교해서 긴 문자열을 반환하는 람다식.
reduce는 내부적으로 스트림을 구성하는 문자열의 길이를 비교, 마지막에는 결과를 반환.
병렬스트림
하나의 작업을 둘 이상의 작업으로 나눠 동시에 진행. -> 병렬 처리
String str1 = lst.parallelStream()
.reduce("", c1);
System.out.println("str1 = " + str1);
}
parallelStream 메소드를 호출했다는 의미
병렬처리는 CPU 코어 수를 고려하여 적절하게 병렬로 처리