[JAVA]7. 람다(Lambda)와 Stream 둘이 무슨 사이야?🤔

쟈니·2023년 10월 23일

JAVA

목록 보기
7/8
post-thumbnail

람다를 아십니까?😯

웹 개발을 할때에도, 코딩테스트를 할때에도 한번쯤은 들어본 이름, 람다
하지만 친하지는 않았다..(만날 때마다 어색한 사이였다..😥)
오늘은 익명 함수 람다(lamda)에 대해 복기하고 기존 코드들을 람다식으로 리팩토링해볼 것이다.

람다

: 익명 함수를 의미한다.

  • 익명 함수는 일급 객체라는 특징을 가진다.

👍 람다의 장점

  1. 불필요한 반복문이 삭제되어 코드가 간결해진다.
  2. 지연연산을 수행하여 불필요한 연산을 줄인다.
  3. 멀티쓰레드를 활용하여 병렬처리가 가능하다.

👎 람다의 단점

  1. 호출이 어렵다.
  2. ✅stream 사용 시 단순 for문과 while문 성능 저하
  3. 잦은 사용은 가독성을 떨어뜨린다.

🔎일급 객체

다른 객체들에 적용 가능한 연산을 모두 지원하는 객체.
함수를 값으로 사용할 수 있고 파라미터로 전달하거나 변수에 대입하는 등의 연산이 가능하다.

람다 예제

  • 단일 실행문이라면 {} 생략 가능
  • return문으로만 구성되었을 때는 {} 생략 불가!
// 람다식 X
public int sum(int a, int b) {
	return a + b;
}
 
// 람다식 O
(int a, int b) -> {return a + b;}

(String str)-> str.length()

(int x) -> x + 1
(int x) -> {return x + 1;}

@FunctionalInterface

: 구현해야 할 추상 메소드가 하나만 정의된 인터페이스를 의미한다.

@FunctionalInterface 예제

// 구현 메소드가 한 개일 때 가능하다.
// 두 개의 인자를 받는 인터페이스
@FunctionalInterface
public interface Math{
	public int Calc(int a, int b);
}
// 구현 메소드가 한 개일 때 가능하다.
// 두 개의 인자를 받는 Interface Math를 구현할 때 익명 함수를 사용한다.
public static void main(String[] args){
	Math plusLambda = (a,b) -> a+b;
	Math minusLambda = (a, b) -> a-b;;
}

🔎 java.util.function

: java에서 지원하는 function 인터페이스 또한 익명 함수를 이용하여 구현하다.(참고 :[JAVA] 람다식(Lambda)의 개념 및 사용법)

Stream

: 다양한 데이터를 표준화된 방법으로 다루기 위한 라이브러리다. 람다식을 함께 사용한다.
- 데이터를 변경하지 않는 1회성 연산이다.
- 지연 연산을 수행한다.
- 병렬 실행이 가능하다.

구성

  1. 스트림 생성
  2. 중간 연산(스트림 변환) : 연속 수행이 가능하다.
  3. 최종 연산 (스트림 사용) : 마지막에 단 한번만 사용 가능

stream 예제

import java.util.*;
...
int [] arr = {1,2,3,4,5};
ArrayList <Integer> list = new ArrayList<>();
	for(int i =0; i<arr.length; i+=2){
		list.add(arr[i]);
	}
    
return list.stream().mapToInt(i->i).toArray();
/**
	1. list.stream() :list로 steram()생성한다.
    2. mapToInt(i->i) : map의 Type을 Int로 형 변환 (요소 개수만큼 반복 수행)
    3. toArray() : 한 번에 Stream의 요소를 배열로 반환
*/

1. Stream 종류

1. Stream<T> : 범용 Stream
2. IntStream : 값 타입이 Int인 Stream
3. LongStream : 값 타입이 Long인 Stream
4. DoubleStream : 값 타입이 Double인 Stream

2. 중간 연산 명령어

1. Stream <T> distint() : Stream 요소 중복 제거
2. Stream <T> sort() : Stream 요소 정렬
3. Stream <T> filter(Predicate <T> predicate) : 조건에 충족하는 요소를 Stream으로 생성
4. Stream <T> limit(long maxSize) : maxSize 까지의 요소를 Stream로 생성
5. Stream <T> skip(long n) : 처음 n개의 요소를 제외하는 Stream 생성
6. Stream <T> peek(Consumer<T> action) : T타입 요소에 맞는 작업 수행
7. Stream <R> flatMap(Funcion<T, stream<? extends R>> Tmapper) : T 타입 요소를 1:N의 R타입 요소로 변환한 Stream 생성
8. Stream <R> map(Funcion<? super T,? extends R> mapper) : 입력 T타입을 R타입 요소로 변환한 Stream 생성
9. Stream mapToInt() : map Type을 Int로 변환
10. Stream mapToLong() : map Type을 Long로 변환
11. Stream mapToDouble() : map Type을 Double로 변환

3. 최종 연산 명령어

1. void forEach(Consumer <? super T> action) : Stream의 각 요소에 지정된 작업 수행
2. long count() : Stream의 요소 개수
3. Optional <T> sum (Comparator <? super T> comparator) : Stream의 요소 합
4. Optional <T> max (Comparator <? super T> comparator) : Stream 요소의 최대 값
5. Optional <T> min (Comparator <? super T> comparator) : Stream 요소의 최소 값
6. Optional <T> findAny() : Stream 요소의 랜덤 요소
7. Optional <T> findFirst() : Stream 요소의 첫번째 요소
8. boolean allMatch(Pradicate <T> p) : Stream의 값이 모두 만족하면 true
9. boolean anyMatch(Pradicate <T> p) : Stream의 값이 하나라도 만족하면 true
10. boolean noneMatch(Pradicate <T> p) : Stream의 값이 하나라도 만족하지 않으면 false
11. Object[] toArray() : Stream의 모든 요소를 배열로 반환

stream은 언제 사용해야 할까?

  • 원소들에 전체적으로 수행될 작업을 함수적 인터페이스(@FunctionalInterface)를 사용해서 처리할 때 사용-> 가독성이 For-loop보다 좋다
  • 병렬 처리가 효율적인 상황에서 사용

stream은 언제 사용하면 안될까?

  • sort처럼 모든 원소 확인해야 할 때 느리다.
  • 원소 처리하는 함수만 제공하여 처리하는 내부 반복자는 루프 퓨전과 쇼트서킷을 신경쓰지 않아도 된다. (참고 :[Java] Stream API 기초 정리)

🔎 루프 퓨전과 쇼트 서킷

  • 루프 퓨전 : 파이프라인에서 연속적으로 체이닝된 복수의 스트림 연산을 하나의 연산과정으로 병합하는 기법-> 스트림 요소의 접근을 최소화한다.
  • 쇼트 서킷 : 불필요한 연산을 의도적으로 수행하지 않음으로써 실행 속도를 높이는 기법-> 스트림의 일부 요소들에 대한 연산을 완전히 생략한다.
    (참고 :https://bugoverdose.github.io/development/stream-lazy-evaluation/)

📔 참고

[JAVA] 람다식(Lambda)의 개념 및 사용법
[Java] Stream API 기초 정리
https://bugoverdose.github.io/development/stream-lazy-evaluation/

profile
시작은 미미하나 끝은 쥬쥬하다.

0개의 댓글