일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 스프링 고급 - 스프링 aop
- 자바 중급1편 - 날짜와 시간
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch2
- 자바 고급2편 - 네트워크 프로그램
- 자바 고급2편 - io
- 자바의 정석 기초편 ch14
- 데이터 접근 기술
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch6
- 자바 기초
- 자바의 정석 기초편 ch7
- 자바로 계산기 만들기
- 자바의 정석 기초편 ch5
- 스프링 mvc2 - 검증
- 스프링 mvc2 - 타임리프
- 자바 중급2편 - 컬렉션 프레임워크
- 자바의 정석 기초편 ch1
- 2024 정보처리기사 수제비 실기
- 스프링 mvc2 - 로그인 처리
- 람다
- 자바의 정석 기초편 ch9
- 스프링 입문(무료)
- 스프링 트랜잭션
- @Aspect
- 스프링 mvc1 - 스프링 mvc
- 자바로 키오스크 만들기
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch12
- Today
- Total
개발공부기록
람다가 필요한 이유, 프로젝트 환경 구성, 람다가 필요한 이유, 함수 Vs 메서드, 람다 시작 본문
람다가 필요한 이유, 프로젝트 환경 구성, 람다가 필요한 이유, 함수 Vs 메서드, 람다 시작
소소한나구리 2025. 3. 26. 19:37프로젝트 환경 구성

프로젝트 설정
Build: IntelliJ
JDK: 21 이상
람다가 필요한 이유
값 전달
람다를 제대로 이해하려면 내부 클래스에 대한 개념을 확실하게 이해해야 함
지금 설명하는 내용은 익명 클래스 활용에 대한 부분을 일부 심화한 내용임
Ex0Main - 리팩토링 전
package lambda.start;
public class Ex0Main {
public static void helloJava() {
System.out.println("프로그램 시작");
System.out.println("Hello Java");
System.out.println("프로그램 종료");
}
public static void helloSpring() {
System.out.println("프로그램 시작");
System.out.println("Hello Spring");
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
helloJava();
helloSpring();
}
}
helloJava() 메서드와 helloSpring() 메서드는 코드의 중복이 있어 아래처럼 메서드를 하나로 통합할 수 있음
Ex0RefMain - 리팩토링 후
package lambda.start;
public class Ex0RefMain {
public static void hello() {
System.out.println("프로그램 시작"); // 변하지 않는 부분
System.out.println("Hello Java");
System.out.println("Hello Spring");
System.out.println("프로그램 종료"); // 변하지 않는 부분
}
public static void hello(String str) {
System.out.println("프로그램 시작"); // 변하지 않는 부분
System.out.println(str);
System.out.println("프로그램 종료"); // 변하지 않는 부분
}
public static void main(String[] args) {
hello("Hello Java");
hello("Hello Spring");
}
}
핵심은 변하는 부분과 변하지 않는 부분을 분리하는 것으로 변하는 부분은 그대로 유지하고 변하지 않는 부분을 어떻게 해결할 것인가에 집중하면 됨
여기서 "Hello Java", "Hello Spring" 같은 문자열은 상황에 따라 변하는데, 이렇게 상황에 따라 변하는 문자열 데이터를 매개변수(파라미터)를 통해 외부에서 전달받아 출력하면 변하지 않는 부분은 그대로 유지하고 변하는 부분은 전달 받아서 처리하여 코드 중복이 해결이 됨
매우 단순한 문제였지만 프로그래밍에서 중복을 제거하고 좋은 코드를 유지하는 핵심은 변하는 부분과 변하지 않는 부분을 분리하는 것임
변하지 않는 "프로그램 시작", "프로그램 종료"를 출력하는 부분을 그대로 유지하고 상황에 따라 변화가 필요한 문자열은 외부에서 전달 받아서 처리하여 메서드(함수)의 재사용성을 높일 수 있게 됨
리팩토링 전과 후를 비교해보면 hello(String str) 메서드의 재사용성은 매우 높아졌으며 핵심은 변하는 부분을 메서드 내부에서 가지고 있는 것이 아니라 외부에서 전달 받는다는 점임
값 매개변수화(Value Parameterization)
지금 예제에서 변하는 부분은 "Hello Java", "Hello Spring"과 같은 문자값(Value)인데, 이 부분을 String str 매개변수(파라미터)를 사용하여 문자값을 매개변수로 만들었음
문자값, 숫자값 처럼 구체적인 값(Value)을 메서드(함수) 안에 두는 것이 아니라 매개변수(파라미터)를 통해 외부에서 전달 받도록 하여 동작을 달리하고, 재사용성을 높이는 방법을 값 매개변수화(Value Parameterization)라 함
값 매개변수화, 값 파라미터화 등으로 불림
동작 전달
Ex1Main - 리팩토링 전
package lambda.start;
public class Ex1Main {
public static void helloDice() {
long startNs = System.nanoTime();
//코드 조각 시작
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
//코드 조각 종료
long endNs = System.nanoTime();
System.out.println("실행 시간: " + (endNs - startNs) + "ns");
}
public static void helloSum() {
long startNs = System.nanoTime();
//코드 조각 시작
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
//코드 조각 종료
long endNs = System.nanoTime();
System.out.println("실행 시간: " + (endNs - startNs) + "ns");
}
public static void main(String[] args) {
helloDice();
helloSum();
}
}
이전 Ex0Main의 예시와 같이 하나의 메서드에서 실행할 수 있도록 리팩토링을 하려면 변하는 문자열 값을 매개변수화 해서 외부에서 전달하는 것을 넘어서 코드 조각을 전달해야 함
Procedure - 리팩토링을 위한 인터페이스
package lambda;
public interface Procedure {
void run();
}
- 이후에 다른 곳에서도 사용할 예정이므로 상위 패키지에 생성하였음
Ex1RefMain - 리팩토링 후
package lambda.start;
public class Ex1RefMain {
public static void hello(Procedure procedure) {
long startNs = System.nanoTime();
//코드 조각 시작
procedure.run();
//코드 조각 종료
long endNs = System.nanoTime();
System.out.println("실행 시간: " + (endNs - startNs) + "ns");
}
static class Dice implements Procedure {
@Override
public void run() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
}
}
static class Sum implements Procedure {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
}
}
public static void main(String[] args) {
hello(new Dice());
hello(new Sum());
}
}
리팩토링 전의 코드를 보면 helloDice(), helloSum() 메서드에서 시간을 측정하고 시간을 출력하는 부분은 변하지 않는 부분이고, 코드 조각을 시작하고 종료하는 부분은 변하는 부분임
중복을 제거하고 재사용성을 늘리려면 결국에는 코드 조각을 시작하고 종료하는 부분을 외부에서 전달 받아야 하는데 이것은 단순히 문자열, 숫자 같은 값 데이터를 전달 받는 것과는 차원이 다른 문제임
외부에서 코드 조각을 전달하는 방법
코드 조각은 보통 메서드(함수)에 정의하기 때문에 코드 조각을 전달하기 위해서는 메서드가 필요함
그러나 지금까지 학습한 내용으로는 메서드만 전달할 수 있는 방법이 없으므로 대신 인스턴스를 전달하고 인스턴스에 있는 메서드를 호출하면 됨
그러기 위해서 작성한 코드가 Procedure 인터페이스와 내부 정적 클래스인 Dice와 Sum임
내부 정적 클래스로 구현한 Dice, Sum 클래스는 Procedure 인터페이스를 구현하고 run() 메서드에 필요한 코드 조각을 구현하였음(외부에 따로 클래스를 만들어도 됨)
두개의 메서드를 hello()라는 하나의 메서드로 합치고 Procedure procedure를 매개변수(파라미터)를 통해 인스턴스를 전달하면 이 인스턴스의 run() 메서드를 실행하여 필요한 코드 조각을 실행할 수 있게 됨
이때 다형성을 활용하여 외부에서 전달되는 인스턴스에 따라 각각 다른 코드 조각이 실행되게 됨
main 메서드에서 hello() 메서드를 호출할 때 어떤 인스턴스를 전달하는 가에 따라서 다른 결과가 실행되는 것을 확인할 수 있음
new Dice()를 생성하여 호출하면 주사위 로직이, new Sum()을 생성하여 호출하면 계산 로직이 수행됨
정리
- 문자열, 숫자 같은 값 데이터를 메서드에 전달할 때는 String, int와 같은 각 데이터에 맞는 값을 전달하면 됨
- 코드 조각을 메서드에 전달할 때는 인스턴스를 전달하고 해당 인스턴스에 있는 메서드를 호출하면 됨
동작 매개변수화(Behavior Parameterization)
코드 조각(코드의 동작 방법, 로직, Behavior)을 메서드(함수) 안에 두는 것이 아니라 매개변수(파라미터)를 통해 외부에서 전달 받도록 하여 메서드의 동작을 다르게하고 재사용성을 높이는 방법을 동작 매개변수화라 함
동작 매개변수화, 동작 파라미터화, 행동 매개변수화(파라미터화), 행위 파라미터화 등으로 불림
값, 동작 매개변수화 정리
- 값 매개변수화: 값(숫자, 문자열 등)을 바꿔가며 메서드(함수)의 동작을 달리함
- 동작 매개변수화: 어떤 동작(로직)을 수행할지를 메서드(함수)에 전달(인스턴스 참조, 람다 등)
자바에서 동작 매개변수화를 하려면 클래스를 정의하고 해당 클래스를 인스턴스로 만들어서 전달해야 하는데 자바 8에서 등장한 람다를 사용하면 코드 조각을 매우 편리하게 전달할 수 있음
익명 클래스 사용
Ex1RefMainV3
package lambda.start;
// 익명 클래스를 사용
public class Ex1RefMainV3 {
// hello() 코드 동일 생략
public static void main(String[] args) {
hello(new Procedure() {
@Override
public void run() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
}
});
hello(new Procedure() {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
}
});
}
}
익명 클래스를 사용하여 Ex1RefMain과 같은 기능을 하는 코드를 구현할 수 있음
단순히 익명클래스의 참조값을 별도의 변수로 뽑아서 전달하는 코드이므로 Ex1RefMainV2는 생략하고 익명클래스의 참조값을 hello()메서드에 직접 전달하는 Ex1RefMainV3의 코드만 공유함
람다(Lambda)
자바에서 메서드의 매개변수에 인수로 전달할 수 있는 것은 크게보면 int, double과 같은 기본형 타입과 Procedure, Member와 같은 참조형 타입(인스턴스) 두 가지임
이렇게 코드 조각을 전달하기 위해서는 클래스를 정의하고 메서드를 만들고 인스턴스까지 생성하는 복잡한 과정을 거쳐야하는데, 자바 8에 들어서면서 람다라는 것을 통해 코드 블럭을 인수로 전달할 수 있는 큰 변화가 생겼음
Ex1RefMainV4
package lambda.start;
public class Ex1RefMainV4 {
// hello() 코드 동일 생략
public static void main(String[] args) {
hello(() -> {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
});
hello(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
});
}
}
hello() 메서드 안에 () -> { ... } 부분이 람다를 사용한 코드이며 실행 결과는 기존과 같음
람다를 사용한 코드를 보면 클래스나 인스턴스를 정의하지 않고 매우 간편하게 코드 블럭을 직접 정의하고 전달하는 것을 확인할 수 있는데 람다를 제대로 이해하기 위해서는 함수에 대해서 알아야 함
함수 Vs 메서드
함수(Function)와 메서드(Method)는 둘 다 어떤 작업(로직)을 수행하는 코드의 묶음임
하지만 일반적으로 객체지향 프로그래밍(OOP) 관점에서 아래와 같은 차이가 있음
예시
C 언어
// C에서는 클래스나 객체가 없으므로, 모든 것이 함수
int add(int x, int y) {
return x + y;
}
Java
// 자바에서는 클래스 내부에 함수를 정의함 -> 메서드
public class Calculator {
// 인스턴스 메서드
public int add(int x, int y) {
return x + y;
}
}
// 사용 예
Calculator cal = new Calculator();
int result = cal.add(2, 3); // 'add'는 메서드
Python
# 함수: 클래스 밖에서 독립적으로 정의
def add(x, y):
return x + y
# 메서드: 클래스(객체) 내부에 정의
class Calculator:
def add(self, x, y):
return x + y
# 사용 예
print(add(2, 3)) # 함수 호출
cal = Calculator()
print(cal.add(2, 3)) # 메서드 호출
객체(클래스)와의 관계
함수(Function)
- 독립적으로 존재하며, 클래스(객체)와 직접적인 연관이 없음
- 객체지향 언어가 아닌 C등의 절차적 언어에서는 모든 로직이 함수 단위로 구성됨
- 객체지향 언어라 하더라도 Python이나 JavaScript 처럼 클래스 밖에서도 정의할 수 있는 "함수" 개념을 지원하는 경우 이를 함수라고 부름
메서드(Method)
- 클래스(또는 객체)에 속해 있는 "함수"임
- 객체의 상태(필드, 프로퍼티 등)에 직접 접근하거나, 객체가 제공해야 할 기능을 구현할 수 있음
- Java, C++, C#, Python 등 대부분의 객체지향 언어에서 클래스 내부에 정의된 함수는 보통 "메서드"라고 부름
호출 방식과 스코프
함수(Function)
- 호출 시에 객체 인스턴스가 필요 없음
- 보통 이름(매개변수) 형태로 호출됨
- 지역 변수, 전역 변수 등과 함께 동작하며 클래스나 객체 특유의 속성(인스턴스 변수 등)은 다루지 못함
메서드(Method)
- 보통 객체(인스턴스).메서드이름(매개변수) 형태로 호출함
- 호출될 때 해당 객체의 필드(속성)나 다른 메서드에 접근 가능하며 이를 이용해 수행함
- 인스턴스 메서드, 클래스(정적) 메서드, 추상 메서드 등 다양한 형태가 있을 수 있음
정리
- 메서드는 기본적으로 클래스(객체) 내부의 함수를 가리키며 객체의 상태와 밀접한 관련이 있음
- 함수는 클래스(객체)와 상관없이 독립적으로 호출 가능한 로직의 단위임
- 메서드는 객체지향에서 클래스 안에 정의하는 특별한 함수라고 생각하면 됨
- 함수와 메서드는 수행하는 역할 자체는 같지만 소속(클래스 or 독립)과 호출 방식에서 차이가 난다고 이해하면 됨
람다 시작
익명 클래스를 람다로
매개변수가 없는 익명 클래스를 람다로 변환
package lambda.lambda1;
public class ProcedureMain1 {
public static void main(String[] args) {
Procedure procedure = new Procedure() {
@Override
public void run() {
System.out.println("hello! lambda");
}
};
procedure.run();
}
}
public class ProcedureMain2 {
public static void main(String[] args) {
Procedure procedure = () -> {
System.out.println("hello! lambda");
};
procedure.run();
}
}
매개변수가 없고 반환값이 없는데 ProcedureMain1의 run() 메서드는 ProcedureMain2의 코드 처럼 람다로 작성할 수 있음
람다는 () -> { ... } 와 같이 표현되는데 () 부분이 메서드의 매개변수라고 생각하면 되고 { ... } 부분이 코드 조각이 들어가는 본문임
람다를 사용할 때는 이름, 반환 타입은 생략하고 매개변수와 본문만 간단하게 적으면 되므로 익명 클래스를 사용하는 것 보다 람다를 사용할 때 코드가 훨씬 간결한 것을 확인할 수 있음
(매개변수) -> { 본문 } 이지만 여기에서는 () -> { 본문 }이 되었음
매개변수가 있는 익명 클래스를 람다로 변환
package lambda;
public interface MyFunction {
int apply(int a, int b);
}
- MyFunction은 앞으로 여러 곳에서 사용할 예정이므로 패키지 위치를 상위 패키지에 두었음
package lambda.lambda1;
public class MyFunctionMain1 {
public static void main(String[] args) {
MyFunction myFunction = new MyFunction() {
@Override
public int apply(int a, int b) {
return a + b;
}
};
int result = myFunction.apply(1, 2);
System.out.println("result = " + result);
}
}
public class MyFunctionMain2 {
public static void main(String[] args) {
MyFunction myFunction =(int a, int b) -> {
return a + b;
};
int result = myFunction.apply(1, 2);
System.out.println("result = " + result);
}
}
MyFunctionMain1의 int apply(int a, int b) 메서드는 매개변수가 2개가 있고 int를 반환하는데 마찬가지로 람다로 변경할 수 있음
매개변수가 있으므로 (int a, int b) -> { 본문 } 과 같이 간편하게 람다로 작성할 수 있음
IDE의 도움을 받으면 지금보다 더 간단한 모양의 람다식이나, 메서드 참조 라는 것으로 변경할 수 있는데 이부분은 람다를 깊게 공부하면서 알아볼 예정임
출처 : 인프런 - 김영한의 실전 자바 - 고급3편 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용함
'자바 로드맵 강의 > 고급 3 - 람다, 스트림, 함수형 프로그래밍' 카테고리의 다른 글
함수형 인터페이스, 함수형 인터페이스와 제네릭, 람다와 타겟 타입, 함수형 인터페이스(기본, 특화, 기타) (0) | 2025.04.04 |
---|---|
람다, 람다 정의, 함수형 인터페이스, 람다와 시그니처, 람다와 생략, 람다의 전달, 고차 함수 (0) | 2025.04.03 |