Java 유지보수 코드 리팩토링 3 - 람다, 익명 클래스
이전 시간에 작성한 정적 중첩 클래스를 지역 클래스로 변경한다.
Process.class
public interface Process {
void run();
}
Dice.class
import org.w3c.dom.ls.LSOutput;
import java.util.Random;
public class Dice {
public static void hello(Process proc) {
System.out.println("프로그램 시작");
// 코드 시작
proc.run();
// 코드 종료
System.out.println("프로그램 종료");
System.out.println();
}
static class ThrowDice implements Process{
public void run() {
int random = new Random().nextInt(6) + 1;
System.out.println("주사위 굴린 결과: " + random);
}
}
static class Sum implements Process{
public void run() {
// 코드 시작
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
// 코드 종료
}
}
public static void main(String[] args) {
ThrowDice dice = new ThrowDice();
Sum sum = new Sum();
hello(dice);
hello(sum);
}
}
프로그램 시작
주사위 굴린 결과: 3
프로그램 종료
프로그램 시작
i = 0
i = 1
i = 2
프로그램 종료
리팩토링1 - 지역 클래스로 변경하기
주어진 코드를 지역 클래스로 변경하였다.
Dice.class
import org.w3c.dom.ls.LSOutput;
import java.util.Random;
public class Dice {
public static void hello(Process proc) {
System.out.println("프로그램 시작");
// 코드 시작
proc.run();
// 코드 종료
System.out.println("프로그램 종료");
System.out.println();
}
public static void main(String[] args) {
class ThrowDice implements Process{
public void run() {
int random = new Random().nextInt(6) + 1;
System.out.println("주사위 굴린 결과: " + random);
}
}
class Sum implements Process{
public void run() {
// 코드 시작
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
// 코드 종료
}
}
ThrowDice dice = new ThrowDice();
Sum sum = new Sum();
hello(dice);
hello(sum);
}
}
Dice 클래스에서 ThrowDice, Sum 클래스를 main 메소드로 지역 클래스로 만들면서 옮겼다.
- 정적 중첩 클래스를 지역 클래스로 변경
- 기존 코드를 main 메소드로 이동 (선택사항)
- 전달하는 코드가 변경될 수 있으므로 호출부인 main 메소드로 옮겼음
- Dice 클래스는 코드를 전달받아 출력하는 역할임
리팩토링2 - 익명 클래스로 변경하기
리팩토링한 지역클래스를 익명 클래스로 변경하도록 한다.
익명 클래스 선언과 생성하기
Dice.class
import org.w3c.dom.ls.LSOutput;
import java.util.Random;
public class Dice {
public static void hello(Process proc) {
System.out.println("프로그램 시작");
// 코드 시작
proc.run();
// 코드 종료
System.out.println("프로그램 종료");
System.out.println();
}
public static void main(String[] args) {
Process dice = new Process() {
@Override
public void run() {
int random = new Random().nextInt(6) + 1;
System.out.println("주사위 굴린 결과: " + random);
}
};
Process sum = new Process() {
@Override
public void run() {
// 코드 시작
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
// 코드 종료
}
};
hello(dice);
hello(sum);
}
}
- 지역 클래스의 선언 과 생성 별도로 진행 부분이 있었다.
- 익명 클래스로 변경하면서 선언과 생성을 한번에 처리한다.
익명 클래스를 메소드 인자로 전달하기
Dice.class
import org.w3c.dom.ls.LSOutput;
import java.util.Random;
public class Dice {
public static void hello(Process proc) {
System.out.println("프로그램 시작");
// 코드 시작
proc.run();
// 코드 종료
System.out.println("프로그램 종료");
System.out.println();
}
public static void main(String[] args) {
hello(new Process() {
@Override
public void run() {
int random = new Random().nextInt(6) + 1;
System.out.println("주사위 굴린 결과: " + random);
}
});
hello(new Process() {
@Override
public void run() {
// 코드 시작
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
// 코드 종료
}
});
}
}
- hello 인자로 익명 클래스로 전달함
익명 클래스로 호출부에서 코드를 만들어 해당 함수로 전달해 실행하는 모습을 볼 수 있다. 이러한 특징은 다른 언어에서 콜백함수, 델리게이트, 익명함수 기능과 유사한 개념이 있다.
그리고 자바 개발자는 해당 익명 클래스의 편의성이 문제가 있다.
hello(new Process() {
@Override
public void run() {
// 코드 시작
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
// 코드 종료
}
});
전달하고자 하는 코드에서 인스턴스를 생성하고 run() 메소드를 생성해 전달하는 모습을 볼 수 있다.
자바 개발자가 원하는건 인스턴스 생성과 run() 메소드 없이 실행한 것을 원한다.
람다(Lambda)
앞서 말한 코드에서 hello() 호출 인자로 "new Process() {...}" 생성 코드를 전달하는 모습을 볼 수 있는데
hello({
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
// 코드 종료
});
생성 부분을 제거하고 위의 코드처럼 편의성을 높이는 방법을 찾게 되었다.
이 방법은 람다(Lambda) 함수 기능으로 자바8에서 지원하게 되었다.
람다 이전 코드 스타일
- int, double 같은 기본형으로 인자 전달
- Process, Member 참조형 타입 전달
메소드로 인수로 전달하는 것은 간단한 데이터나 인스턴스의 참조이다.
람다 필요성
public runDice() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
}
public void runSum() {
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
}
hello(runDice());
hello(runSum());
메소드(runDice, runSum)를 만들고 인스턴스를 생성하여 전달할 필요가 있는지 생각해 볼 수 있다.
위의 코드처럼 클래스나 인스턴스 관계 없이 메소드만 전달하면 더 간단할 수 있다.
자바8에서는 큰 변화로 이러한 인자로 메소드를 전달할 수 있게 되었다.
기능의 이름은 람다(Lambda)이다.
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);
}
});
익명 클래스 대신 람다만 사용해도 되지 않을까?
익명 클래스는 멤버변수 선언이 가능하여 사용할 수 있으므로 익명 클래스도 필요하다.