본문 바로가기
💻 개발/Java

자바 기본 - 람다와 스트림

by 컴쏘 2024. 11. 7.

 

최근 참여하고 있는 챌린지에서, 강사님이 람다와 스트림을 사용해야 하는 이유에 대해 말씀해주셨다. 그래서 공부를 해보고자 한다. 

 

람다 표현식

람다(lambda)란 Java에서 간결하게 익명 함수(이름 없는 함수)를 표현하는 방식이다.

 

람다 표현식은 함수형 프로그래밍을 구성하기 위한 함수식이다. 

 

람다식으로 표현하면 메서드 이름, 매개변수 타입과 반환 값을 생략할 수 있고, 이를 변수에 넣어 자바 코드를 간결하게 만들 수 있다.

  • 타입을 생략해도 컴파일러가 생략된 타입 위치를 추론하여 동작하게 해주기 때문에 에러가 나지 않는다. 
  • 람다식은 인터페이스를 익명 클래스로 구현한 익명 구현 객체를 짧게 표현한 것이다. 
    • 따라서, 오로지 인터페이스로 선언한 익명 구현 객체만 람다식으로 표현 가능하다. 
    • 람다 표현이 가능한 인터페이스함수형 인터페이스라고 말한다. 
    • 람다는 일급 객체이다. 

+) 일급 객체? 

더보기

일급 객체는 사용할 때 다른 요소들과 아무런 차별이 없이 사용할 수 있는 객체이다. 그리고 밑의 3가지 요건을 충족해야 한다. 

 

1. 변수나 데이터에 담을 수 있어야 한다. 

2. 함수의 파라미터로 전달 할 수 있어야 한다. 

3. 함수의 리턴값으로 사용할 수 있어야 한다. 

 

함수형 인터페이스

딱 하나의 추상 메소드가 선언된 인터페이스 

  • 인터페이스의 final 상수, default, static, private 메서드는 추상 메서드가 아니기 때문에 이들이 여러 개 있어도 추상 메서드가 1개라면 함수형 인터페이스로 취급된다. 
  • @FuntionalInterface 어노테이션을 붙여주면 2개 이상의 메소드를 선언할 시 컴파일 오류를 발생시킨다. 

 

람다의 한계은 없을까? 

람다의 한계

  1. 문서화가 어렵다. (이름이 없는 함수이기 때문에, 주석이나 문서화가 제한된다.) 
  2. stream에서 람다를 사용할 시 for문 보다 성능이 떨어진다. (Stream API은 내부 반복을 통해 컬렉션을 처리한다. Stream API의 내부 반복은 반복 처리 방식에 람다 표현식과 함수형 인터페이스를 포함하고, 파이프라인으로 연결된 여러 단계를 거친다. 따라서 추가적인 오버헤드가 발생할 수 있다.) 

 

스트림

스트림은 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소이다. 

 

스트림의 특징은 다음과 같다. 

  • 연속된 요소 : 컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값집합의 인터페이스를 제공한다.
    • 컬렉션이 중점을 두는 관점과 역할은 데이터(ArrayList, LinkedList,...) 
    • 스트림이 중점을 두는 관점과 역할은 데이터 처리 및 계산(filter, sorted, map, ...) 
  • 데이터 소스 : 스트림은 컬렉션, 배열, I/O 자원 등의 데이터 소스로부터 데이터를 가져와 가공하고 처리한다. 
    • 스트림은 단순히 원본 데이터를 가져와 처리하는 역할을 하므로, 원본 데이터의 순서를 바꾸지 않는다.
    • 정렬된 컬렉션으로 스트림을 생성하면 정렬이 그대로 유지된다. 
  • 데이터 처리 연산 : 스트림은 데이터를 필터링, 변환, 집계하는 등의 연산을 수행할 수 있다. 
    • filter(조건에 맞는 요소 걸러내기), map(각 요소를 특정 규칙에 따라 변환하기), reduce(모든 요소를 하나로 합치기), find(특정 조건을 만족하는 요소를 찾기), match(특정 조건을 만족하는 요소인지 검사하기), sort(데이터 정렬하기), ... 
    • 데이터 처리 연산은 순차적으로 또는 병렬적으로 실행 가능하다. 
  • 파이프라이닝 : 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 반환한다. 
    • 대부분의 스트림 연산은 자신을 반환하기 때문에 여러 스트림 연산을 연결할 수 있다. 
    • 이를 메서드 체이닝이라고 한다. (이러한 방식으로 큰 파이프라인을 쉽게 구성 가능)
  • 내부 반복 : 반복자를 사용하여 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원한다. 

 

+) 더 알아보기 : 스트림과 컬렉션 

더보기

가장 큰 차이점은 데이터를 언제 계산하는 지이다. 

 

컬렉션 (Collection)

현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료 구조 

 

이는, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다. 

 

컬렉션은 사용자가 직접 요소를 반복(for-each, while, ... )해야 하는데 이를 외부 반복이라고 한다. 

 

스트림 (Stream)

이론적으로 요청할 때만 요소를 계산하는 고정된 자료구조 (사용자가 요청할 때만 값을 계산한다.) 

 

스트림은 단 한번만 소비할 수 있다. 

 

스트림은 반복을 알아서 처리해주고 결과 스트림 값을 어딘가에 저장해주는 내부 반복을 사용한다. 

 

스트림(Stream) 연산

스트림 연산은 크게 2가지로 구분할 수 있다. 

  • 중간 연산 : 연결할 수 있는 스트림 연산을 중간 연산이라고 한다. 
    • filler나 sorted 같은 중간 연산은 다른 스트림을 반환한다. 
    • 따라서, 여러 중간 연산을 연결해서 질의를 만들 수 있다. 
    • 중간 연산은 최종 연산이 실행되기 전까지 아무 연산도 수행하지 않는다. Lazy 중간 연산은 이러한 특정 덕분에 최적화 효과를 얻을 수 있다. 
  • 최종 연산 : 스트림을 닫은 연산을 최종 연산이라고 한다. 
    • 스트림 파이프라인에서 결과를 도출한다. 
    • 보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환된다. 
    • 중간 연산을 이용해서 파이프라인을 구성할 수 있지만 최종 연산 없이는 결과를 반환할 수 없다. 

 

참고 1
참고 2
참고 3

'💻 개발 > Java' 카테고리의 다른 글

자바 기본 - System.out.println()  (0) 2024.11.07
자바 기본 - 어노테이션과 리플렉션  (0) 2024.11.07
자바 기본 - 제네릭  (0) 2024.11.06
자바 기본 - 예외  (0) 2024.11.06
자바 기본 - 문자열  (0) 2024.11.05