Notice
Recent Posts
Recent Comments
«   2025/02   »
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
Archives
Today
Total
관리 메뉴

SYDev

[이것이 자바다] Chapter 16. 람다식 본문

Programming Lang/Java

[이것이 자바다] Chapter 16. 람다식

시데브 2025. 2. 2. 19:58

1.  람다식이란?

  • functional programming: 함수를 정의하고, 이 함수를 데이터 처리부로 보내 데이터를 처리하는 기법
  • 데이터 처리부는 데이터만 가지고 있으며, 처리 방법이 정해져 있지 않아 외부에서 제공된 함수에 의존

  • 동일한 데이터라도 어떤 함수를 제공하는지에 따라, 처리하는 결과가 다를 수 있음 -> 데이터 처리의 다형성이라고도 볼 수 있음
람다식: (매개변수, ...) -> { 처리 내용 }
  • Java는 람다식을 익명 구현 객체로 변환

 

예를 들어, 다음과 같은 인터페이스가 존재할 때

public interface Calculable {
    // abstract method
    void calculate(int x, int y);
}

 

해당 interface의 익명 구현 객체는 다음과 같이 생성 가능

new Caculable() {
    @Override
    public void calcuate(int x, int y) { 처리내용 }
};

 

이를 람다식으로 표현하면 아래 형태 - 람다식인터페이스의 익명 구현 객체 -> 인터페이스 타입의 매개변수에 대입 가능

(x, y) -> { 처리내용 };

 

다음과 같은 Caculable 매개변수를 가진 action() method에 대입 가능

public void action(Calculable calculable) {
    int x = 10;
    int y = 4;
    calculable.calculate(x, y);    // 데이터를 제공하고 추상 메소드를 호출
}

// action method 호출
action( (x, y) -> {
    int result = x + y;
    System.out.println(result);
});

 

  • 인터페이스의 익명 구현 객체를 람다식으로 표현하려면, 인터페이스가 단 하나의 abstract method만 가져야 함
  • functional interface: 단 하나의 abstract method만 가지는 interface
  • @FunctionalInterface: 인터페이스가 함수형 인터페이스임을 보장 -> abstract method가 하나인지 검사

 

2. 매개변수가 없는 람다식

() -> {
    실행문;
    실행문;
}

() -> 실행문    // 실행문이 하나일 경우에만 중괄호 생략 가능

- 예제 1

package ch16.sec2.exam1;

@FunctionalInterface
public interface Workable {
	void work();
}

package ch16.sec2.exam1;

public class Person {
	public void action(Workable workable) {
		workable.work();
	}
}

package ch16.sec2.exam1;

public class LambdaExample {
	public static void main(String[] args) {
		Person person = new Person();
		
		// 실행문이 두 개 이상인 경우 중괄호 필요
		person.action(() -> {
			System.out.println("출근을 합니다.");
			System.out.println("프로그래밍을 합니다.");
		});
		
		// 실행문이 한 개일 경우 중괄요 생략 가능 
		person.action(() -> System.out.println("퇴근합니다."));
	}
}

- 예제 2

package ch16.sec2.exam2;

public class Button {
	// static member interface
	@FunctionalInterface
	public static interface ClickListener {
		// abstract method
		void onClick();
	}
	
	// field
	private ClickListener clickListener;
	
	// method
	public void setClickListener(ClickListener clickListener) {
		this.clickListener = clickListener;
	}
	
	public void click() {
		this.clickListener.onClick();
	}
}

package ch16.sec2.exam2;

public class ButtonExample {
	public static void main(String[] args) {
		// OK 버튼 객체 생성
		Button btnOk = new Button();
		
		// OK 버튼 객체에 람다식(ClickListener 익명 구현 객체) 주입
		btnOk.setClickListener(() -> {
			System.out.println("OK 버튼을 클릭했습니다.");
		});
		
		// OK 버튼 클릭하기
		btnOk.click();
		
		// Cancel 버튼 객체 생성
		Button btnCancel = new Button();
		
		btnCancel.setClickListener(() -> {
			System.out.println("Cancel 버튼을 클릭했습니다.");
		});
		
		// Cancel 버튼 클릭하기
		btnCancel.click();
	}
}

 

3. 매개변수가 있는 람다식

  • 타입을 생략하고 작성하는 것이 일반적
  • 구체적 타입 대신에 var 사용 가능
// 타입 작성
(타입 매개변수, ...) -> {
    실행문;
    실행문;
}

(타입 매개변수, ...) -> 실행문

// var
(var 매개변수, ...) -> {
    실행문;
    실행문;
}

(var 매개변수, ...) -> 실행문

// 타입 생략
(매개변수, ...) -> {
    실행문;
    실행문;
}

(매개변수, ...) -> 실행문
  • 매개변수가 하나일 경우 괄호 생략 가능, 이때 타입 또는 var 붙일 수 없음

 

4. 리턴값이 있는 람다식

(매개변수, ...) -> {
    실행문;
    return 값;
}

// return 키워드 생략
(매개변수, ...) -> 값

- 예제

package ch16.sec4;

public class Person {
	public void action(Calculable calculable) {
		double result = calculable.calc(10, 4);
		System.out.println("결과: " + result);
	}
}

package ch16.sec4;

@FunctionalInterface
public interface Calculable {
	double calc(double x, double y);
}
package ch16.sec4;

public class LambdaExample {
	public static void main(String[] args) {
		Person person = new Person();
		
		// 실행문이 두 개 이상일 경우
		person.action((x, y) -> {
			double result = x + y;
			return result;
		});
		
		// 리턴문이 하나만 있을 경우(연산식)
		person.action((x, y) -> (x + y));
		
		// 리턴문이 하나만 있을 경우(메소드 호출)
		person.action((x, y) -> sum(x, y));
	}
		
	public static double sum(double x, double y) {
		return (x + y);
	}
}

 

5. 메소드 참조

  • 메소드 참조는 메소드를 참조해서 매개변수의 정보 및 리턴 타입을 알아내 람다식에서 불필요한 매개변수를 제거하는 목적
  • ex) 두 개의 값을 받아 큰 수를 리턴하는 Math 클래스의 max() static method를 호출하는 람다식
(left, right) -> Math.max(left, right);

 

위 람다식을 다음과 같이 메소드 참조로 표현 가능

Math :: max;

5.1. 정적 메소드와 인스턴스 메소드 참조

클래스 :: 메소드   // static method
참조변수 :: 메소드  // instance method

5.2. 매개변수의 메소드 참조

  • 람다식에서 제공되는 a 매개변수의 메소드를 호출해서, b 매개변수값을 매개값으로 사용하는 경우 존재
(a, b) -> { a.instanceMethod(b); }

 

위 람다식을 다음과 같이 표현 가능

클래스 :: instanceMethod

-> 작성 방법은 static method 참조와 동일, but a의 instance method가 사용된다는 점에서 차이 존재

- 예제

package ch16.sec5.exam2;

public interface Comparable {
	int compare(String a, String b);
}

package ch16.sec5.exam2;

public class Person {
	public void ordering(Comparable comaprable) {
		String a = "홍길동";
		String b = "김길동";
		
		int result = comaprable.compare(a, b);
		
		if(result < 0) {
			System.out.println(a + "은 " + b + "보다 앞에 옵니다.");
		} else if(result == 0) {
			System.out.println(a + "은 " + b + "과 같습니다.");
		} else {
			System.out.println(a + "은 " + b + "보다 뒤에 옵니다.");
		}
	}
}
package ch16.sec5.exam2;

public class MethodReferenceExample {
	public static void main(String[] args) {
		Person person = new Person();
		// (a, b) -> a.compareToIgnoreCase(b)
		person.ordering( String :: compareToIgnoreCase );
	}
}

 

6. 생성자 참조

  • 생성자 참조 -> 객체를 생성하는 것을 의미
  • 람다식이 단순히 객체를 생성하고 리턴하도록 구성 -> 람다식을 생성자 참조로 대치 가능
(a, b) -> { return new 클래스(a, b); }

 

위 람다식을 다음과 같이 표현 가능

클래스 :: new
  • 생성자가 오버로딩되어 여러 개 존재하는 경우
    • 컴파일러는 functional interface의 abstract method와 동일한 매개변수 타입과 개수를 가지고 있는 생성자를 찾아 실행
    • 해당 생성자가 존재하지 않으면 컴파일 오류 발생

- 예제

package ch16.sec5.exam3;

@FunctionalInterface
public interface Creatable1 {
	public Member create(String id);
}

package ch16.sec5.exam3;

@FunctionalInterface
public interface Creatable2 {
	public Member create(String id, String name);
}

package ch16.sec5.exam3;

public class Member {
	private String id;
	private String name;
	
	public Member(String id) {
		this.id = id;
		System.out.println("Member(String id)");
	}
	
	public Member(String id, String name) {
		this.id = id;
		this.name = name;
		System.out.println("Member(String id, String name)");
	}
	
	@Override
	public String toString() {
		String info = "{ id: " + id + ", name: " + name + " }";
		return info;
	}
}

package ch16.sec5.exam3;

public class Person {
	public Member getMember1(Creatable1 creatable) {
		String id = "winter";
		Member member = creatable.create(id);
		return member;
	}
	
	public Member getMember2(Creatable2 creatable) {
		String id = "winter";
		String name = "한겨울";
		Member member = creatable.create(id, name);
		return member;
	}
}
package ch16.sec5.exam3;

public class ConstructorReferenceExample {
	public static void main(String[] args) {
		Person person = new Person();
		
		Member m1 = person.getMember1( Member :: new );
		System.out.println(m1);
		System.out.println();
		
		Member m2 = person.getMember2( Member :: new );
		System.out.println(m2);
	}
}


참고자료