람다 표현식이란?
람다 표현식은 자바 8에 도입된 기능으로, 함수형 프로그래밍 스타일을 자바에 적용할 수 있게 해줍니다. 이는 단 하나의 추상 메서드를 가진 인터페이스(함수형 인터페이스)의 구현을 간결하게 표현할 수 있도록 돕습니다. 익명 내부 클래스를 작성하는 것에 비해 훨씬 적은 코드로 동일한 기능을 구현할 수 있어 코드의 가독성과 유지보수성을 향상시킵니다.
람다 표현식의 기본 문법은 다음과 같습니다:
(매개변수 목록) -> { 메서드 본문 }
- 매개변수 목록: 괄호 안에 0개 이상의 매개변수를 지정합니다. 타입 추론이 가능하므로 대부분 생략할 수 있습니다.
->(람다 연산자): 매개변수 목록과 메서드 본문을 구분하는 화살표 토큰입니다.- 메서드 본문: 람다가 수행할 작업을 정의합니다. 단일 표현식인 경우 중괄호
{}와return키워드를 생략할 수 있습니다. 여러 문장인 경우 중괄호{}를 반드시 사용해야 하며, 값을 반환할 때는return키워드를 명시해야 합니다.
기본 사용 예시
1. 사용자 정의 함수형 인터페이스
람다 표현식은 함수형 인터페이스에만 적용될 수 있습니다.
@FunctionalInterface 어노테이션을 사용하여 컴파일러에게 해당 인터페이스가 단 하나의 추상 메서드만 가지도록 강제할 수 있습니다.
@FunctionalInterface
interface TaskExecutor {
void perform(String operation);
}
2. 익명 내부 클래스와 람다 비교
다음 예시는 익명 내부 클래스를 사용하여 TaskExecutor 인터페이스를 구현하는 전통적인 방식과 람다 표현식을 사용한 간결한 방식을 비교합니다.
public class LambdaComparison {
public static void main(String[] args) {
// 1. 전통적인 익명 내부 클래스 방식
TaskExecutor oldWay = new TaskExecutor() {
@Override
public void perform(String operation) {
System.out.println("익명 클래스 실행: " + operation);
}
};
oldWay.perform("데이터 로드");
// 2. 람다 표현식을 사용한 간결한 방식
TaskExecutor newWay = (op) -> System.out.println("람다 표현식 실행: " + op);
newWay.perform("파일 처리");
}
}
출력:
익명 클래스 실행: 데이터 로드
람다 표현식 실행: 파일 처리
3. 매개변수 및 반환 값 처리
람다는 매개변수가 없거나, 여러 개의 매개변수를 가지거나, 값을 반환하는 등 다양한 형태의 메서드 시그니처를 지원합니다.
@FunctionalInterface
interface SimpleAction {
void execute();
}
@FunctionalInterface
interface NumericCalculator {
int calculate(int val1, int val2);
}
public class LambdaSignatureTest {
public static void main(String[] args) {
// 매개변수 없는 람다
SimpleAction myAction = () -> System.out.println("매개변수 없이 작업 수행");
myAction.execute();
// 여러 매개변수와 반환 값이 있는 람다 (단일 표현식)
NumericCalculator adder = (x, y) -> x + y;
int sumResult = adder.calculate(15, 25);
System.out.println("두 수의 합: " + sumResult);
// 여러 문장을 포함하는 람다 (중괄호와 return 사용)
NumericCalculator complexCalc = (numA, numB) -> {
System.out.println("계산 중: " + numA + " * " + numB);
int product = numA * numB;
return product;
};
System.out.println("두 수의 곱: " + complexCalc.calculate(7, 6));
}
}
컬렉션 스트림 API와 람다
람다 표현식은 자바 8의 스트림(Stream) API와 결합될 때 강력한 시너지를 발휘합니다. 스트림은 컬렉션 데이터를 처리하는 추상화된 방법을 제공하며, 람다를 통해 데이터를 필터링, 매핑, 정렬하는 등의 작업을 간결하게 수행할 수 있습니다.
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamLambdaExample {
public static void main(String[] args) {
List<String> fruitList = new ArrayList<>();
fruitList.add("Apple");
fruitList.add("Banana");
fruitList.add("Cherry");
fruitList.add("Date");
fruitList.add("Fig");
// 1. 모든 요소 순회 및 출력 (forEach)
System.out.println("--- 모든 과일 ---");
fruitList.forEach(item -> System.out.println("과일: " + item));
// 2. 특정 조건으로 필터링 (filter)
List<String> longNames = fruitList.stream()
.filter(s -> s.length() > 5)
.collect(Collectors.toList());
System.out.println("\n--- 이름이 5자 초과하는 과일 ---");
System.out.println(longNames);
// 3. 요소 변환 (map)
List<String> upperCaseFruits = fruitList.stream()
.map(String::toUpperCase) // 메서드 참조 사용
.collect(Collectors.toList());
System.out.println("\n--- 대문자로 변환된 과일 ---");
System.out.println(upperCaseFruits);
// 4. 정렬 (sorted)
List<String> sortedFruits = fruitList.stream()
.sorted((s1, s2) -> s1.compareTo(s2)) // 알파벳 순으로 정렬
.collect(Collectors.toList());
System.out.println("\n--- 알파벳 순으로 정렬된 과일 ---");
System.out.println(sortedFruits);
}
}
메서드 참조로 람다 간소화
람다 표현식이 단순히 기존 메서드를 호출하는 역할을 할 때는 메서드 참조(Method Reference)를 사용하여 코드를 더욱 간결하게 만들 수 있습니다. 이는 특정 람다 형태의 축약형이라고 볼 수 있습니다.
import java.util.Arrays;
import java.util.List;
public class MethodReferenceExample {
// 예시로 사용할 정적 메서드
public static void displayItem(String item) {
System.out.println("항목: " + item);
}
public static void main(String[] args) {
List<String> productNames = Arrays.asList("Laptop", "Monitor", "Keyboard");
// 람다 표현식 원래 형태
System.out.println("--- 람다 표현식 ---");
productNames.forEach(name -> displayItem(name));
// 정적 메서드 참조로 간소화
System.out.println("\n--- 메서드 참조 ---");
productNames.forEach(MethodReferenceExample::displayItem);
}
}
람다 사용 시 유의사항
- 함수형 인터페이스에만 적용: 람다는 단일 추상 메서드를 가진 함수형 인터페이스에만 사용할 수 있습니다. 여러 개의 추상 메서드를 가진 인터페이스에는 적용할 수 없습니다.
- 캡처링 변수의 제약: 람다 내부에서 외부 스코프의 지역 변수를 사용할 경우, 해당 변수는
final이거나 사실상final(effectively final)이어야 합니다. 즉, 람다 외부에서 해당 변수의 값을 변경할 수 없습니다. this의 의미: 람다는 자신만의this컨텍스트를 가지지 않습니다. 람다 내부의this는 람다를 선언한 외부 클래스의 인스턴스를 참조합니다. 이는 익명 내부 클래스와의 주요 차이점 중 하나입니다.- 가독성 유지: 복잡한 로직을 무리하게 한 줄 람다로 압축하기보다는, 가독성을 최우선으로 고려하여 여러 줄의 람다 또는 일반 메서드를 사용하는 것이 좋습니다.