1. 간결하고 가독성이 좋은 코드: 람다를 사용해서 가독성이 좋아진다.
2. 병렬 처리 가능: 병렬 스트림을 사용하면 멀티코어 프로세서의 장점을 활용하여 빠른 속도로 대량의 데이터를 처리할 수 있다.
3. 지연 연산(lazy evaluation): 필요한 데이터만 처리하고 나머지 데이터는 처리하지 않는다는 의미 - 성능을 향상
4. 강력한 기능: 맵(map), 필터(filter), 정렬(sort), 그룹화(grouping) 등 다양한 연산을 스트림을 통해 수행할 수 있습니다.
지연 연산(lazy evaluation)
불필요한 연산을 안한다.
아래의 코드는 1~10까지의 정수를 갖는 List에서 6보다 작고, 짝수인 요소를 찾아 10배 시킨 리스트를 출력하는 코드입니다.
final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(
list.stream()
.filter(i -> i<6)
.filter(i -> i%2==0)
.map(i -> i*10)
.collect(Collectors.toList())
);
//결과 [20, 40]
이 코드의 연산방식을 생각해보면
1. 1~10까지 요소들 중 6보다 작은 값들을 구하고
2. 1번에서 구한 요소들 중 짝수인 값들을 구하고
3. 2번에서 구한 요소들에 10을 곱해준다.
라고 생각할 수 있지만 그렇지 않다.
[그러한 방식은 Eager Evaluation]
실제로 어떤 방식으로 동작하는지 테스트 해보기 위해 아래와 같이 코드를 조금 수정해서 실행시켜 보면,
i < 6
i%2 == 0
i < 6
i%2 == 0
i = i*10
i < 6
i%2 == 0
i < 6
i%2 == 0
i = i*10
i < 6
i%2 == 0
i < 6
i < 6
i < 6
i < 6
i < 6
[20, 40]
1. 6보다 작은지 검사한다. ( false 이면 2, 3번 과정 패스하고 다음 요소 진행 )
2. 짝수인지 검사한다. ( false 이면 3번 과정 패스하고 다음 요소 진행 )
3. 요소에 10을 곱해준다.
Lazy 방식은 앞의 조건을 완료 해야 다음 연산을 실행 하도록 되어 있다.
예제 2)
아래의 코드는 1~10까지의 정수를 갖는 List에서 6보다 작고, 짝수인 요소를 찾아 10을 곱한 리스트 중 첫 번째 요소를 출력하는 코드입니다.
final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(
list.stream()
.filter(i -> i<6)
.filter(i -> i%2==0)
.map(i -> i*10)
.findFirst()
.get()
);
//결과 20
위의 코드가 만약 Eager Evaluation 방식으로 동작한다면,
1. 모든 요소들에 대해 6보다 작은지 체크하고,
2. 1번에서 구한 모든 요소들에 대해 짝수인지 체크하고,
3. 2번에서 구한 모든 요소들에 10을 곱해주고,
4. 3번의 요소들 중 첫 번째 요소를 반환한다.
final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(
list.stream()
.filter(i -> {
System.out.println("i < 6");
return i<6;
})
.filter(i -> {
System.out.println("i%2 == 0");
return i%2==0;
})
.map(i -> {
System.out.println("i = i*10");
return i*10;
})
.findFirst()
.get()
);
실제로 코드를 찍어서 실행 시켜보면
아래와 같은 결과 값을 얻을 수 있다.
i < 6
i%2 == 0
i < 6
i%2 == 0
i = i*10
20
순차적으로 연산을 진행하다가 원하는 값인 첫 번째 요소의 값을 구하면 나머지 연산을 하지 않는것을 볼 수 있다.
[Eager Evaluation]
[Lazy Evaluation]
예제3)
파라미터로 받아온 value를 출력하는 다음과 같은 메소드가 있다.
private static void getValueUsingMethodResult(boolean valid, String value) {
if(valid)
System.out.println("Success: The value is "+value);
else
System.out.println("Failed: Invalid action");
}
아래의 코드와 같이 매개 변수 value에 실행시간이 1초 이상 걸리는 메소드의 결과를 넘겨준다고 한다면,
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
getValueUsingMethodResult(true, getExpensiveValue());
getValueUsingMethodResult(false, getExpensiveValue());
getValueUsingMethodResult(false, getExpensiveValue());
System.out.println("passed Time: "+ (System.currentTimeMillis()-startTime)/1000+"sec" );
}
private static String getExpensiveValue() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello World";
}
getValueUsingMethodResult() 3번을 전부 실행하여 3초의 실행결과를 얻게 된다.
Success: The value is Hello World
Failed: Invalid action
Failed: Invalid action
passed Time: 3sec
getValueUsingMethodResult()를 조금 수정해서,
String value 대신 Functional Interface 인 Supplier<T> (아래 설명 있음)를 매개 변수로 전달하는 getValueUsingSupplier() 메소드를 생성하면
private static void getValueUsingSupplier(boolean valid, Supplier<String> valueSupplier) {
if(valid)
System.out.println("Success: The value is "+valueSupplier.get());
else
System.out.println("Failed: Invalid action");
}
이 경우에는 메소드의 반환 값이 아닌 함수가 전달되었기 때문에, valueSupplier.get() 을 호출할 때만,
getExpensiveValue() 메소드가 호출됩니다.
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
getValueUsingSupplier(true, () -> getExpensiveValue());
getValueUsingSupplier(false, () -> getExpensiveValue());
getValueUsingSupplier(false, () -> getExpensiveValue());
System.out.println("passed Time: "+ (System.currentTimeMillis()-startTime)/1000+"sec" );
}
private static String getExpensiveValue() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello World";
}
Success: The value is Hello World
Failed: Invalid action
Failed: Invalid action
passed Time: 1sec
위와 같이 1초만에 같은 결과를 얻을 수 있게 됩니다.
Supplier<T> 란 ? 👇
Supplier 인터페이스는 get() 메서드를 단 하나의 추상 메서드로 가지며, 이 메서드는 인자를 받지 않고 T 타입의 값을 반환한다.
@FunctionalInterface
public interface Supplier<T> {
T get();
}
-----------------------------------
public class SupplierExample {
public static void main(String[] args) {
Supplier<String> supplier = () -> "Hello, World!";
String result = supplier.get();
System.out.println(result);
}
}
//결과
Hello, World!
Supplier는 인자 값을 받지 않고 타입 그대로를 반환 시켜준다. - 주로 스트림이나 람다를 사용해야 할때 사용
Supplier는 다른 Functional Interface와 함께 사용되기도 한다.
예를 들어, Predicate와 함께 사용하여 조건을 만족하는 데이터를 Supplier를 통해 생성할 수도 있습니다.
Supplier<T>는 일반적으로 지연 초기화, 데이터 제공 등의 상황에서 유용하게 활용할 수 있는 Functional Interface입니다.
'개-발 > Java + Spring + Kotlin' 카테고리의 다른 글
[Spring] AOP 를 활용한 중복요청 방지 (따닥방지) (2) | 2023.12.19 |
---|---|
[JAVA] Stream 이해 (Parallelism 병렬처리) (0) | 2023.06.12 |
[JAVA] TDD / Testcode (mockito) (0) | 2023.05.07 |
[JAVA] TDD / Testcode (0) | 2023.05.07 |
[Spring] JDA(Java Discord Api)를 이용한 자동 방 생성 (0) | 2023.03.09 |