주제
- Lazy Evalution이란 무엇이고 어떻게 동작하고 어떨 때 사용할 수 있을까
목차
주제목차내용💨What✅How스트림 동작 방식 알아보기내가 생각한 stream의 흐름실제 동작Eager evaluation 방식Lazy Evaluation 방식 code 비교🤪Lazy Evaluation을 통해 효율적인 동작을 이끌어낼 수 있는 다른 예🏌️Performance Improve❓Why 📌 REFER
내용
LazyEvaluation💨What
게으른 이라는 뜻으로, 불필요한 연산을 피하기 위해 연산을 지연시키는 것으로
불필요한 연산을 피한다!
Supplier F.I 의 get() 메소드가 Lazy Evaluation이 가능하다.
✅How
스트림 동작 방식 알아보기
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; }) .collect(Collectors.toList()) );
해당 결과는 어떻게 될까?
내가 생각한 stream의 흐름
- 첫번째 필터에서 6보다 작은 것들을 고르고 나서
- 그 중에서 짝수인 것을 찾고
- 그것들을 기반으로 10을 곱한다.
- 그리고 그 결과를 기반으로 List 로 변환한다.
결과는 ❌
실제 동작
각 요소들은 순차적으로 아래 1~3번을 진행합니다.
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]
- 6보다 작은지 검사한다.
- 짝수인지 검사한다.
- 요소에 10을 곱해준다.
이렇듯 Lazy방식은 당장에 해결해야할 문제들이 차례로 주어지더라도 마지막 문제를 제공받을 때 까지 게으르다가 기다리다가 마지막 문제를 알게되면, 그때 연산을 시작함으로써 결과를 얻기 위해 필요하지 않은 연산은 수행하지 않게 된다.
Eager evaluation 방식

Lazy Evaluation 방식

code 비교
- 같은 동작을 하는 반복문을 이용한 코딩과 비교해보기
final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int resultInt=0; for (Integer i : list) { if(i<6) { if(i%2==0) { i*=10; resultInt = i; break; } } } System.out.println(resultInt); Colored by Color Scripter
- 함수형 프로그래밍을 사용함으로써 훨씬 가독성이 좋은 것을 볼 수 있다.
- 파라미터로 받아온 Value를 출력하는 다음과 같은 메소드가 있다.
🤪Lazy Evaluation을 통해 효율적인 동작을 이끌어낼 수 있는 다른 예
- Lazy Evaluation을 통해 효율적인 동작을 이끌어낼 수 있는 다른 예
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"); } 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"; }
- 위 메소드를 호출할 때, 아래의 코드와 같이 매개 변수 value에 실행시간이 1초 이상 걸리는 메소드의 결과를 넘겨준다고 한다면, boolean valid의 값에 관계 없이 getExpensiveValue() 메소드가 호출되기 때문에 아래 결과를 얻디까지 3초가 경과하게 된다.
Success: The value is Hello World Failed: Invalid action Failed: Invalid action passed Time: 3sec
🏌️Performance Improve
- getValueUsingmethodResult()를 조금 수정해서 String value 대신 Functional Interface인 Supliier<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"); } public static void main(String args[]){ getValueUsingSupplier(true, () -> getExpensiveValue()); getValueUsingSupplier(false, () -> getExpensiveValue()); getValueUsingSupplier(false, () -> getExpensiveValue()); }
Success: The value is Hello World Failed: Invalid action Failed: Invalid action passed Time: 1sec
결과적으로 value 값에 관계 없이 무조건 호출하지 않고 get()을 호출했을 때 메소드가 호출되것을 볼 수 있다. 이로써 얻을 수 있는 이점은 자원 생성과 반납이 무거운 작업에서는 해당 코드를 이렇게 사용함으로써 부하를 줄일 수 있으며 불필요한 리소스도 줄일 수 있기 때문에 포퍼먼스 적으로 최고의 방안이라 할 수 있다.
- 이와 같이 전보다는 2초 더 빨리 개선된 것을 볼 수 있다.
❓Why
스트림이란 일련의 파이프라인을 이은 것으로써 한번 시작되면 끝까지 진행된다는 특성이 있다. 그래서 첫번째에서 정제하고 그 다음 선별된 것들 중 고르는 방식의 iterator 방식과는 다르다. 해당 특성을 이용함으로써 성능을 최대로 이끌어주곤 한다.