람다란: 함수형 프로그래밍을 지원하는 개념 중 하나로 이를 사용하면 메서드를 하나의 식으로 표현할 수 있으며, 코드를 간결하게 만들어주고 불필요한 코드를 제거할 수 있습니다.
람다 표현식의 기본 형식
(매개변수) -> { 실행 코드 블록 }
매개변수는 메서드의 매개변수와 유사하게 작성하고, 실행 코드 블록 내부에는 메서드의 내용을 작성합니다. 이때 중괄호 {}는 코드 블록을 나타내며, 람다식의 실행 코드가 포함됩니다.
// 기존의 방식 (익명 내부 클래스 사용)
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello, World!");
}
};
// 람다 표현식 사용
Runnable runnable2 = () -> {
System.out.println("Hello, World!");
};
위의 예시에서 람다 표현식을 사용하면 익명 내부 클래스를 정의하지 않고도 코드를 간결하게 표현할 수 있습니다.
람다표현식의 단축형태을 설명하자면 람다 표현식은 매우 간단한 경우에는 더 간결하게 표현할 수 있습니다. 예를 들어, 매개변수가 하나이고 실행 코드 블록 내부에 단일문장(한 줄)이 있다면 중괄호와 세미콜론을 생략할 수 있습니다.
// 기존의 방식 (익명 내부 클래스 사용)
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello, World!");
}
};
// 람다 표현식 사용 (단축 형태)
Runnable runnable2 = () -> System.out.println("Hello, World!");
더욱 다양한 예시들 보겠습니다.
첫 번째 예제에서는 메서드가 매개변수도 없고 반환 값도 없는 형태입니다. 람다 표현식에서는 ()로 매개변수 부분을 표현하며, 중괄호 {} 내부에 실행 코드를 작성합니다.
// 기존의 방식 (메서드 사용)
public void print() {
String s = "test";
System.out.println(s);
}
// 람다 표현식 사용 (No Parameter, No Return)
() -> {
String s = "test";
System.out.println(s);
}
두 번째 예제에서는 코드가 한 줄로 간단한 경우로, 중괄호 {}와 세미콜론 ;을 생략한 단축 형태의 람다 표현식입니다.
// 람다 표현식 사용 (No Parameter, No Return, 단축 형태)
() -> System.out.println("test");
세 번째 예제에서는 메서드에 매개변수가 있는 경우를 보여줍니다. 람다 표현식에서 매개변수를 괄호 () 안에 작성하고, 실행 코드 블록 내부에 해당 매개변수를 사용하는 코드를 작성합니다..
// 기존의 방식 (메서드 사용)
public void print(String s) {
System.out.println(s);
}
// 람다 표현식 사용 (With Parameter, No Return)
(String s) -> System.out.println(s)
네 번째 예제에서는 메서드에 매개변수와 반환 값이 있는 경우입니다. 람다 표현식에서는 중괄호 {}를 사용하여 실행 코드를 감싸고, return 키워드를 통해 반환 값을 명시하. return이 있을 시 중괄호는 제거하지 못합니다.
// 기존의 방식 (메서드 사용)
public int add(int x, int y) {
return x + y;
}
// 람다 표현식 사용 (With Parameter, With Return)
(int x, int y) -> { return x + y; }
다섯 번째 예제에서는 단순한 덧셈을 수행하는 메서드를 람다 표현식으로 표현한 것입니다. 단순한 연산이고 반환 값만 있는 경우에는 중괄호 {}와 return을 생략하고 간결하게 표현할 수 있습니다.
// 람다 표현식 사용 (With Parameter, With Return, 단축 형태)
(x, y) -> x + y
람다 표현식은 주로 함수형 인터페이스를 사용하는 곳에서 활용됩니다.
함수형 인터페이스란 단 하나의 추상 메서드를 가지고 있는 인터페이스를 말하며, 람다 표현식은 이 추상 메서드를 구현하는 용도로 사용되며 다양한 함수형 인터페이스에서 람다 표현식을 사용할 수 있습니다.
@FunctionalInterface
public interface Convertible {
void convert(int USD);
}
여기서 Convertible은 하나의 추상 메서드 convert를 가진 함수형 인터페이스입니다. @FunctionalInterface 어노테이션은 선택적이지만, 이를 사용함으로써 인터페이스가 함수형 인터페이스임을 명시적으로 나타낼 수 있습니다.
이제 람다 표현식을 활용해보면:
Convertible convertible = (USD) -> System.out.println(USD + "달러 = " + (USD * 1400) + " 원");
이 람다 표현식은 Convertible 인터페이스의 구현을 제공합니다. 여기서 람다 표현식은 메서드 매개변수, 화살표(->), 그리고 메서드 본문으로 구성되고 (USD)는 convert 메서드의 매개변수를 나타내고, -> 뒤의 부분은 메서드의 구현을 나타냅니다.
메소드의 명칭 또한 람다에서는 생략이 가능하기에 명칭은 따로 기입되지 않았는데 그 이유는, 함수형 인터페이스에서는 단 하나의 추상 메서드만 존재하기 때문에, 람다 표현식을 사용할 때 어떤 메서드를 구현하고 있는지 자동으로 추론됩니다. 따라서 별도로 메서드 이름을 명시할 필요가 없습니다
다음으로 스트림은 배열, 컬렉션 또는 특정 범위의 숫자로부터 생성할 수 있습니다.
먼저 배열에서 스트림을 생성해 보자
int[] scores = {100, 95, 90, 85, 80};
IntStream scoreStream = Arrays.stream(scores);
String[] langs = {"python", "java", "javascript", "c", "c++", "c#"};
Stream<String> langStream = Arrays.stream(langs);
Arrays.stream 메서드를 사용하면 배열에서 스트림을 직접 생성할 수 있습니다.
컬렉션에서 스트림 생성
List<String> langList = Arrays.asList("python", "java", "javascript", "c", "c++", "c#");
Stream<String> langListStream = langList.stream();
컬렉션 객체에 .stream() 메서드를 호출하여 스트림을 생성할 수 있습니다. 이는 컬렉션 내부의 데이터를 스트림으로 변환합니다.
먼저, 문자열을 저장하는 langList라는 이름의 List를 생성합니다. 이 List는 "python", "java", "javascript", "c", "c++", "c#"라는 문자열이 포함되어 있습니다. 추가로 Arrays.asList()는 Java에서 배열을 List로 변환하는 메소드입니다.
langList를 스트림으로 변환하기 위해 stream() 메서드를 사용합니다. stream() 메서드는 List에 정의된 메서드로, List의 모든 요소를 스트림으로 처리할 수 있게 해줍니다. langList.stream()은 langList를 스트림으로 변환하여 langListStream이라는 스트림 객체에 저장합니다.
스트림 연산
스트림 API를 사용하면 데이터에 대해 다양한 연산을 선언적으로 수행할 수 있습니다. 이러한 연산은 '중간 연산'과 '최종 연산'으로 구분됩니다.
중간 연산
중간 연산은 스트림의 데이터를 변환하거나, 특정 조건에 맞는 데이터를 선택하는 등의 작업을 수행합니다.
// 90점 이상인 점수 선택
Arrays.stream(scores).filter(x -> x >= 90).forEach(x -> System.out.println(x));
// 4글자 이하의 언어를 선택 후 대문자로 변환
langList.stream().filter(x -> x.length() <= 4).map(String::toUpperCase).forEach(System.out::println);
최종 연산
최종 연산은 스트림의 데이터에 대해 집계, 반복, 조건 검사 등의 작업을 수행하며 스트림의 데이터를 소모합니다.
// 90점 이상인 사람의 수 계산
int count = (int) Arrays.stream(scores).filter(x -> x >= 90).count();
System.out.println(count);
// c로 시작하는 프로그래밍 언어 찾기
Arrays.stream(langs).filter(x -> x.startsWith("c")).forEach(System.out::println);
스트림의 유연성
스트림은 다양한 데이터 소스와 다양한 연산을 지원함으로써 데이터 처리에 있어 매우 유연합니다. 예를 들어, 하나의 스트림에 여러 중간 연산을 적용할 수 있으며, 다양한 최종 연산을 사용하여 원하는 결과를 얻을 수 있습니다.
// 4글자 이하의 언어 중에서 c를 포함하는 언어를 찾고, "(어려워요)"를 추가하여 출력
langList.stream()
.filter(x -> x.length() <= 4)
.filter(x -> x.contains("c"))
.map(x -> x + "(어려워요")
.forEach(System.out::println);
이 예시는 조건에 맞는 데이터를 찾아 변환한 후 출력하는 과정을 한 줄의 스트림 연산으로 표현합니다.
'Java' 카테고리의 다른 글
| 커스텀 예외 처리 방법 (0) | 2024.12.20 |
|---|---|
| [동시성 문제] - HashMap / ConcurrentHashMap (0) | 2024.08.25 |
| Java (익명 클래스) (1) | 2024.01.06 |
| Java (컬렉션 프레임워크) (0) | 2024.01.06 |
| Java (제네릭스) (1) | 2024.01.04 |