✅ 함수형 프로그래밍이란?!
함수형 프로그래밍은 말 그대로 "함수 중심으로 생각하는 방식" 이다.
java에서는 원래 객체 지향(OOP) 중심으로 코드를 작성했지만,
점점 코드를 더 간결하고 실수없이 동시에 처리하기 쉽게 만들고 싶어서 함수형 스타일이 도입됐다.
그래서 Java 8부터는 람다식, 스트림, Optional 같은 기능을 넣어서 함수처럼 동작하는 코드를 사용한다.
// 전통적인 방식
for (String name : list) {
if (name.startsWith("A")) {
System.out.println(name);
}
}
// 함수형 스타일
list.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
✅ 함수형 인터페이스란?
한 개의 메서드만 가지는 인터페이스. 즉, "이 인터페이스는 딱 하나의 동작만 정의한다"는 의미.
@FunctionalInterface 어노테이션을 사용하면 하나의 메서드만 정의할 수 있고
Anonymous 클래스로 오버라이딩해서 사용할 수 있고 혹은 람다식으로 간단하게 동작 표현이 가능하다.
람다식으로 표현 가능한 이유는 함수형 인터페이스는 하나의 동작만 정의하기 때문이다.
JDK8 : @FunctionalInterface와 함께 기본 함수형 인터페이스가 도입됨.
JDK9~21 : 함수형 인터페이스 자체에는 변화가 없음. 대신 Stream API 확장, Optional개선 등 람다와 함께 쓰기 편해진 정도.
즉, JDK8기준으로 학습해도 충분하고, 이후 버전에서는 문법이나 유틸성 개선이 조금 있었을뿐!
public class Test {
@FunctionalInterface //하나의 메서드만 정의 가능
interface MyFunc {
void something();
//void something2() //2개이상 메서드 등록시 컴파일 오류
}
public static void main(String[] args) {
//Anonymous 방식
MyFunc func1 = new MyFunc() {
@Override
public void something() {
System.out.println("func1 execute !!!");
}
};
func1.something(); // "func1 execute !!!"
//람다식 방식
MyFunc func2 = () -> System.out.println("func2 execute !!!");
func2.something(); // "func2 execute !!!"
}
}
✅ 자바에서 제공하는 대표적인 함수형 인터페이스
리턴값 | 인터페이스 | 파라미터 | 설명 |
void | Runnable | 없음 | 실행만 하고 결과를 반환 하지 않음 (예: 쓰레드 작업 등) |
T | Supplier<T> | 없음 | 파라미터 없이 값을 생성해서 반환함 |
void | Consumer<T> | T | 값을 소비하고 아무것도 반환 하지 않음 |
R | Function<T,R> | T | 입력을 받아서 다른 값으로 변환해서 리턴 |
boolean | Predicate<T> | T | 조건을 검사해서 true/false 반환 |
T | UnaryOperator<T> | T | T 타입을 입력을 받아 동일한 T 타입 결과를 반환함 (Function의 특수형) |
T | BinaryOperator<T> | T,T | 두 개의 T를 받아 하나의 T를 반환 (Function의 특수형) |
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
public class Test {
@FunctionalInterface
interface MyFunc {
void something();
//void something2() //2개이상 메서드 등록시 컴파일 오류
}
public static void main(String[] args) {
// 1. @FunctionalInterface Custom
MyFunc func = () -> System.out.println("@FunctionalInterface !!!");
func.something(); // "@FunctionalInterface !!!"
// 2. Runnable - 매개변수도 반환도 없고 단순 실행
Runnable run = () -> System.out.println("Runnable !!!");
run.run(); // "Runnable !!!"
// 3. Supplier<T> - 매개변수 없이 값 반환
Supplier<String> sup = () -> "Supplier !!!";
System.out.println(sup.get()); // "Supplier !!!"
// 4. Consumer<T> - 매개변수 받아서 리턴없이 작업 실행
String str1 = "Consumer !!!";
Consumer<String> con = (x) -> System.out.println(x);
con.accept(str1); // "Consumer !!!"
// 5. Predicate<T> - 매개변수 받아서 조건 검사후 true/false 리턴
String str2 = "Predicate !!!";
Predicate<String> pre = (x) -> "predicate".equalsIgnoreCase(x);
System.out.println(pre.test(str2)); // "false"
// 6. Function<T, R> - T타입 매개변수 받아서 실행후 R타입 리턴
String str3 = "Function";
Function<String, String> funcc = (x) -> new StringBuilder(x)
.append(" !!!")
.toString();
System.out.println(funcc.apply(str3)); // "Function !!!"
// 7. UnaryOperator<T> - T타입 매개변수 받아서 실행후 T타입 리턴
String str4 = "UnaryOperator";
UnaryOperator<String> un = (x) -> new StringBuilder(x)
.append(" !!!")
.toString();
System.out.println(un.apply(str4)); // "UnaryOperator !!!"
// 8. BinaryOperator<T>
// T타입의 매개변수 두개를 받아서 실행후 T타입 리턴
String str5 = "BinaryOperator";
String Str6 = " !!!";
BinaryOperator<String> bin = (x,y) -> new StringBuilder()
.append(x)
.append(y)
.toString();
System.out.println(bin.apply(str5, Str6)); // "BinaryOperator !!!"
}
}
✅ 람다 표현식이란?!
람다 표현식은 "익명 클랙스(anonymous inner class)를 짧은 문법으로 표현 하는 방식" 이다.
기존 자바에서는 익명 클래스를 이용해 함수를 인라인으로 전달했지만, 문법이 길고 복잡했다.
그래서 함수를 간결하게 표현하고, 메서드를 하나의 '값'처럼 다루는 문법이다.
JDK 버전 | 변화 내용 |
Java 8 | 람다 표현식의 도입 |
Java 9 | 람다 표현식 자체의 변화는 없지만 관련 기능 강화로 활용도 증가 |
Java 11 | var 키워드를 람다 매개변수에 사용할 수 있게됨. 즉, (var x, var y) -> x+y 형식 가능. 주로 어노테이션과 함께 사용. |
java 18+ | 람다 문은 거의 변화 없음. 다만 패턴 매칭, 레코드, 지역변수 개선등과 결합해서 더 유연하게 사용 가능해짐. |
// 기존 방식
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// 람다 표현식
Runnable r = () -> System.out.println("Hello");
✅ 람다 표현식 문법과 함수형 인터페이스와의 연결
👉 람다 표현식 기본 문법
- 매개변수 : 생략 가능 (매개변수 1개일 경우 괄호 생략 가능)
- 중괄호 : 실행문이 단 한 줄이면 생략 가능
- 리턴 : 한 줄이면 return 키워드도 생략가능.
(매개변수) -> { 실행문; }
//다양한 예시
() -> System.out.println("Hello"); //매개변수 없음
x -> System.out.println(x); //매개변수 1개
(x, y) -> x + y; //두개의 매개변수
x -> {return x * 2;} //명시적 return
x -> x*2; //소괄호,중괄호, return 생략
👉 함수형 인터페이스와의 연결
- 람다 표현식은 반드시 함수형 인터페이스를 구현한 형태로 사용됨
- 함수형 인터페이스는 추상 메서드가 하나만 존재하는 인터페이스
- 람다는 "코드를 값처럼 전달" 하는 방식이고, 함수형 인터페이스는 "담는 그릇" 역할이다
@FunctionalInterface
public interface MyFunc {
void doSomething();
}
// 람다로 구현
MyFunc f = () -> System.out.println("실행!");
f.doSomething(); // 출력: "실행!"
✅ 람다식과 익명 클래스 차이점
항목 | 익명 클래스 (Anonymous Class) | 람다식 (Lambda) |
등장 시기 | Java 1.1 | Java 8 |
형태 | 클래스 정의와 구현 포함 | 간결한 문법 |
목적 | 클래스 확장 또는 인터페이스 구현 | 함수형 인터페이스의 간단한 구현 |
this의 의미 | 익명 클래스 자기 자신 | 람다를 감싼 외부 객체 |
가독성 | 길고 복잡할 수 있음 | 짧고 간결 |
성능 | 내부적으로 클래스가 생성됨 (컴파일후 class생성됨을 확인할 수 있음) |
불필요한 클래스 생성 없음 (JVM 최적화) |
✍ 요약
함수형인터페이스를 잘 활용하면 좀 더 간결하고 명시적으로 표현 가능하며
람다식을 사용하면 더 간결하고 효율적으로 사용 가능하다.
하지만 일회성이 아니라 반복적으로 사용하는 메서드라면 별도 구현 후 반복 호출 하는게 낫다.
'Programing' 카테고리의 다른 글
동기/비동기, 블로킹/논블로킹 정리 (0) | 2025.05.14 |
---|---|
[Java] 이미지/파일을 읽는 대표적인 두 가지 방법 (1) | 2025.05.14 |
[Java] Optional Class (0) | 2025.05.08 |
[Java] IO vs NIO (1) | 2025.04.30 |
[Java] Stream 한방 정리 (👆JDK_1.8) (0) | 2025.04.29 |