람다가 필요한 이유
예제 코드 1
다음 코드를 리팩토링해서 코드의 중복을 제거해보자.
public class Ex0Main {
public static void main(String[] args) {
helloJava();
helloSpring();
}
private static void helloJava() {
System.out.println("프로그램 시작");
System.out.println("Hello Java");
System.out.println("프로그램 종료");
}
private static void helloSpring() {
System.out.println("프로그램 시작");
System.out.println("Hello Spring");
System.out.println("프로그램 종료");
}
}다음과 같이 변하는 부분과 변하지 않는 부분을 분리하여 리팩토링 할 수 있다.
public class Ex0RefMain {
public static void main(String[] args) {
hello("Hello Java");
hello("Hello Spring");
}
private static void hello(String str) {
System.out.println("프로그램 시작"); //변하지 않는 부분
System.out.println(str); //변하는 부분
System.out.println("프로그램 종료"); //변하지 않는 부분
}
}여기서 핵심은 변하는 부분과 변하지 않는 부분을 분리하는 것으로, 변하는 부분은 그대로 유지하고 변하는 부분을 어떻게 해결할 것인가에 집중해야 한다.
이렇게 변하는 부분과 변하지 않는 부분을 분리하고, 변하는 부분을 외부에서 전달 받으면 메서드(함수)의 재사용성을 높일 수 있다. 중요한 점은 변하는 부분을 메서드 내부에서 가지고 있는 것이 아니라 외부에서 전달 받는다는 점이다.
👆 값 매개변수화 (Value Parameterization)
위 예제에서는
String str매개변수(파라미터)를 사용해서 문자값을 매개변수로 만들었다.문자나 숫자처럼 구체적인 값을 메서드(함수) 안에 두는 것이 아니라, 매개변수(파라미터)를 통해 외부에서 전달받도록 해서 메서드의 동작을 달리하고, 재사용성을 높이는 방법을 값 매개변수화라고 한다.
예제 코드 2
이번에도 비슷한 코드를 리팩토링해서 코드의 중복을 제거해보자.
public class Ex1Main {
public static void main(String[] args) {
helloDice();
helloSum();
}
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 interface Procedure {
void run();
}public class Ex1RefMain {
static class Sum implements Procedure {
@Override
public void run() {
//코드 조각 시작
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
//코드 조각 종료
}
}
static class Dice implements Procedure {
@Override
public void run() {
//코드 조각 시작
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
//코드 조각 종료
}
}
public static void main(String[] args) {
hello(new Dice());
hello(new Sum());
}
public static void hello(Procedure procedure) {
long startNs = System.nanoTime();
//코드 조각 시작
procedure.run();
//코드 조각 종료
long endNs = System.nanoTime();
System.out.println("실행 시간: " + (endNs - startNs) + "ns");
}
}리팩토링된
hello()메서드에는Procedure procedure매개변수(파라미터)를 통해 인스턴스를 전달할 수 있다. 이 인스턴스의run()메서드를 실행하면 필요한 코드 조각을 실행할 수 있다.이때 다형성을 활용해서 외부에서 전달되는 인스턴스에 따라 각각 다른 코드 조각이 실행된다.
👆 동작 매개변수화 (Behavior Parameterization)
코드 조각을 메서드(함수) 안에 두는 것이 아니라, 매개변수(파라미터)를 통해서 외부에서 전달 받도록 해서 메서드의 동작을 달리하고, 재사용성을 높이는 방식을 동작 매개변수화라고 한다.
예제 코드 3
위 예제를 익명 클래스를 사용해서 리팩토링해보자.
/**
* 익명 클래스 사용
*/
public class Ex1RefMainV2 {
public static void main(String[] args) {
hello(new Procedure() {
@Override
public void run() {
//코드 조각 시작
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
//코드 조각 종료
}
});
hello(new Procedure() {
@Override
public void run() {
//코드 조각 시작
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
//코드 조각 종료
}
});
}
public static void hello(Procedure procedure) {
long startNs = System.nanoTime();
//코드 조각 시작
procedure.run();
//코드 조각 종료
long endNs = System.nanoTime();
System.out.println("실행 시간: " + (endNs - startNs) + "ns");
}
}자바에서 메서드의 매개변수에 인수로 전달할 수 있는 것은 크게 2가지이다.
int,double등의 기본형 타입Procedure,Member등의 참조형 타입 (인스턴스)
메서드에 인수로 전달할 수 있는 것은 간단한 값이나, 인스턴스의 참조이다.
코드 조각을 전달하기 위해 클래스를 정의하고 메서드를 만들고 인스턴스까지 생성하는 것보다는, 클래스나 인스턴스와 관계 없이 직접 코드 블럭을 전달할 수 있다면 더 간단할 것 같다.
자바 8부터는 다음과 같이 람다를 사용하여 코드 블럭을 인수로 전달할 수 있게 되었다.
/**
* 람다 사용
*/
public class Ex1RefMainV3 {
public static void main(String[] args) {
hello(() -> {
//코드 조각 시작
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
//코드 조각 종료
});
hello(() -> {
//코드 조각 시작
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
//코드 조각 종료
});
}
public static void hello(Procedure procedure) {
long startNs = System.nanoTime();
//코드 조각 시작
procedure.run();
//코드 조각 종료
long endNs = System.nanoTime();
System.out.println("실행 시간: " + (endNs - startNs) + "ns");
}
}() -> {...} 부분이 람다를 사용한 코드다. 보면 클래스나 인스턴스를 정의하지 않고, 간편하게 코드 블럭을 직접 정의하고 전달하는 것을 확인할 수 있다.
람다는 함수이다. 람다를 제대로 이해하기 위해서는 함수에 대해 알아야 한다. 먼저 함수와 메서드의 차이를 알아보자.
함수와 메서드
함수와 메서드는 둘 다 어떤 작업을 수행하는 코드의 묶음이다. 하지만 일반적으로 객체지향 프로그래밍 관점에서는 차이가 있다.
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++, Python 등 대부분의 객체지향 언어에서 클래스 내부에 정의된 함수를 보통 메서드라고 부른다.
호출 방식과 스코프
함수 (Function)
호출 시에 객체 인스턴스가 필요없다.
보통
이름(매개변수)형태로 호출된다.지역 변수, 전역 변수 등과 함께 동작하며, 클래스나 객체 특유의 속성(인스턴스 변수 등)은 다루지 못한다.
메서드 (Method)
보통
객체(인스턴스).메서드이름(매개변수)형태로 호출된다.호출될 때 해당 객체의 필드(속성)나 다른 메서드에 접근 가능하며, 이를 이용해 로직을 수행한다.
인스턴스 메서드, 정적 메서드, 추상 메서드 등 다양한 형태가 있을 수 있다.
정리
메서드는 기본적으로 클래스(객체) 내부의 함수를 가리키며, 객체의 상태와 밀접한 관련이 있다.
함수는 클래스(객체)와 상관없이 독립적으로 호출 가능한 로직의 단위이다.
메서드는 객체지향에서 클래스 안에 정의하는 특별한 함수라고 볼 수 있다.
함수와 메서드는 수행하는 역할 자체는 같지만, 소속(클래스 or 독립)과 호출 방식에서 차이가 난다.
Last updated