람다식

이동주·2025년 3월 17일

JAVA

목록 보기
26/30

람다식

  • 데이터 처리부에 제공되는 함수 역할을 하는
  • 함수형 프로그래밍으로, 함수를 정의하고 이 함수를 데이터 처리부로 보내 데이터를 처리하는 기법
  • 데이터 처리부는 제공된 함수의 입력값으로 데이터를 넣고 함수에 정의된 처리 내용을 실행
  • 등록을 목적으로 이벤트 발생 시 실행을 함
  • 자바는 람다식을 익명 구현 객체로 변환함
  • 인터페이스가 존재해야 하며 메소드는 반드시 하나여야 함!
  • 핵심적인 기능만 사용자가 직접 진행할 수 있음!
  • 프레임워크가 제어권을 다 가지고 있음

구조

// 익명 구현 객체
new 클래스명() {
	@Override
    메소드명();
};


// 람다식
(매개변수) -> { 처리내용 };

함수형 인터페이스

  • 인터페이스가 단 하나의 추상메소드를 발생해주는 것

  • 인터페이스가 함수형 인터페이스임을 보장하기 위해서는 @FunctionalInterface 어노테이션을 붙여줘야함

  • @FunctionalInterface
    -> 컴파일 과정에서 해당 인터페이스를 람다 구문으로 사용할 수 있는지 체크하는 어노테이션
    -> 함수가 두 개이면 오류가 발생함

package ch16.sec01;

@FunctionalInterface
// @FunctionalInterface : 람다식으로 사용할 수 있는 인터페이스인지 확인하는 어노테이션
// 메소드가 두 개 이상일 경우 오류 발생
public interface Calculable {
	void calculate(int x, int y);
}
package ch16.sec01;

public class LamdaExample {
	public static void main(String[] args) {
		
		// 람다식 형태 (매개변수) -> {실행문};
		// 익명 클래스의 형태를 조금 더 간략하게 표현한 구문
		action((x,y) -> {
			int result = x + y;
			System.out.println(result);
		});
		
		action((x,y) -> {
			int result = x-y;
			System.out.println(result);
		});
	}
	
	// 정적 메소드 action 선언
	public static void action(Calculable calculable) {
		int x = 10;
		int y = 4;
		
		// Calculable 인터페이스에서 정의된 메소드를 사용
		// 실행은 람다식에서 선언하기 때문에 매개변수만 선언해줘도 됨
		calculable.calculate(x, y);
	}
}

매개변수가 없는 람다식

  • 함수형 인터페이스의 추상 메소드에 매개변수가 없을 경우 람다식 작성
  • 실행문이 하나일 경우에만 중괄호를 생략할 수 있음
  • 실행문이 두 개 이상일 경우에는 중괄호 생략 불가
package ch16.sec02.exam01;

// @FunctionalInterface : 해당 인터페이스가 람다식으로 사용이 가능한지 컴파일러가 판단
@FunctionalInterface
public interface Workable {
	void work();
}
package ch16.sec02.exam01;

public class Person {
	// Workable을 메소드로 가지는 action 메소드
	// 소유권은 나에게 없음
	public void action(Workable workable) {
		workable.work();
	}
}
package ch16.sec02.exam01;

public class LamdaExample {
	public static void main(String[] args) {
		Person person = new Person();
		
		// 구조 : (() -> { 실행문 });
		// 실행문이 두 개 이상일 경우 중괄호를 꼭 붙여줘야함
		person.action(() -> {
			System.out.println("출근");
			System.out.println("프로그래밍");
		});
		
		// 실행문이 하나일 경우 중괄호 생략 가능
		// 해당 실행문에는 세미콜론 붙이지 않기!
		person.action(() -> System.out.println("퇴근"));
	}
}
package ch16.sec02.exam02;

public class Button {
	
	// 정적 멤버 인터페이스 선언
	@FunctionalInterface
	public static interface ClickListener {
		// 추상 메소드
		void onClick();
	}
	
	// 위의 인터페이스를 필드로 사용
	private ClickListener clickListener;
	
	// 외부에서 인터페이스 구현 객체를 받아 
	// 이를 인터페이스 필드에 대입시키는 것
	public void setClickListener(ClickListener clickListener) {
		this.clickListener = clickListener;
	}
	
	// 위의 인터페이스를 동작하기 위한 메소드
	public void click() {
		this.clickListener.onClick();
	}
}
package ch16.sec02.exam02;

public class ButtonExample {
	public static void main(String[] args) {
		
		// 버튼 객체 생성 및 OK 버튼 사용
		Button btnOk = new Button();
		
		// 해당 메소드에 람다식을 이용해 매개값을 전달함
		btnOk.setClickListener(() -> {
			System.out.println("OK 버튼을 클릭했습니다");
		});
		
		// OK 버튼 클릭
		btnOk.click();
		
		// 버튼 객체 생성 및 Cancel 버튼 사용
		Button btnCancel = new Button();
		
		// 해당 메소드에 람다식을 이용해 매개값을 전달
		btnCancel.setClickListener(() -> {
			System.out.println("Cancel 버튼을 클릭했습니다.");
		});
		
		// Cancel 버튼 클릭
		btnCancel.click();
	}
}

매개변수가 있는 람다식

  • 함수형 인터페이스의 추상 메소드에 매개변수가 있을 경우 람다식을 작성함
  • 매개변수를 선언할 때 타입 생략 가능
  • 구체적인 타입 대신 var 사용 가능
  • 매개변수가 하나일 경우 괄호 생략 가능, 하지만 이 때 타입 또는 var을 사용할 수 없음
  • 람다에서는 타입이 중요하지 않고 인자의 갯수만 맞춰주면 됨
  • 타입을 변경할 수 있는 요소가 많기 때문에 타입은 적지 않는 것이 좋음
package ch16.sec03;

@FunctionalInterface
public interface Workable {
	// 인자를 2개 가지는 추상 메소드
	void work(String name, String job);
}
package ch16.sec03;

@FunctionalInterface
public interface Speakable {
	// 인자를 하나 가지는 추상 메소드
	void speak(String content);
}
package ch16.sec03;

public class Person {
	public void action1(Workable workable) {
		// 매개변수를 두 개 가지고 있는 메소드이므로 인자를 두 개 입력함
		workable.work("홍길동", "프로그래밍");
	}
	
	public void action2(Speakable speakable) {
		// 매개변수를 한 개 가지고 있는 메소드이므로 인자를 한 개 입력함
		speakable.speak("안녕하세요");
	}
}
package ch16.sec03;

import java.util.jar.Attributes.Name;

public class LamdaExmaple {
	public static void main(String[] args) {
		Person person = new Person();
		
		// 매개변수가 두 개일 경우
		// 람다식에서는 타입을 신경쓰지 않기 때문에
		// 인자의 갯수만 맞춰서 매개변수를 적어주기만 해도 됨
		// 매개변수가 두 개 이상일 경우 매개변수를 괄호에 감싸서 표기해줘야 함!
		person.action1((name, job) -> {
			System.out.println(name + "이 ");
			System.out.println(job + "을 합니다");
		});
		
		// 한 문장의 경우 중괄호 생략 가능 및 실행문 세미콜론 생략
		person.action1((name, job) -> System.out.println(name + "이 " + job + "을 하지 않습니다"));
		
		// 매개변수가 한 개일 경우
		// 매개변수를 감싸는 괄호 생략이 가능!
		person.action2(word -> { 
			System.out.print("\"" + word + "\"");
			System.out.println("라고 말합니다");
		});
		
		// 실행문이 한 문장일 경우
		// 중괄호 생략 가능 및 세미콜론 생략
		person.action2(word -> System.out.println("\"" + word + "\" 라고 외칩니다."));
		
	}
}

리턴값이 있는 람다식

  • 함수형 인터페이스의 추상 메소드에 리턴 값이 있을 경우 람다식을 작성
  • return 문 하나만 있을 경우에 중괄호와 함께 return 키워드를 생략 가능함
  • 리턴값은 연산식 또는 리턴값이 있는 메소드 호출로 대체 가능함
package ch16.sec04;

@FunctionalInterface
public interface Calculable {
	// double 형태의 리턴값을 가지는 추상 메소드
	double calc(double x, double y);
}
package ch16.sec04;

public class Person {
	// 외부에서 인터페이스 구현 객체를 매개변수로 받아
	// 이를 인터페이스 필드에 대입시킴
	public void action(Calculable calculable) {
		double result = calculable.calc(10, 4);
		System.out.println("결과: " + result);
	}
}
package ch16.sec04;

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) -> x - y);
		
		// 정적 메소드를 사용하고, 실행문이 하나인 경우
		person.action((x, y) -> sum(x,y));
	}
	
	public static double sum(double x, double y) {
		return x+y;
	}
}

메소드 참조

  • 메소드를 참조해 매개변수의 정보 및 리턴 타입을 알아내 람다식에서 불필요한 매개변수를 제거함
  • 람다식의 인자와 매개변수의 갯수가 같을 경우 매개변수를 제거하여 사용 가능
  • 내부적으로 주소가 매핑되어 처리 속도가 매우 빠름!

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

  • 정적 메소드를 참조 시 구조 : 클래스명 :: 정적 메소드명
  • 인스턴스 메소드를 참조 시 구조 : 참조변수명 :: 인스턴스 메소드명
package ch16.sec05.exam01;

//@FunctionalInterface : 람다식으로 사용할 수 있는 인터페이스인지 판단
@FunctionalInterface
public interface Calculable {
	// double 타입 추상 메소드 선언
	double calc(double x, double y);
}
package ch16.sec05.exam01;

public class Person {
	// Calculable 인터페이스를 구현할 객체를 매개변수로 사용
	public void action(Calculable calculable) {
		// Calculable 인터페이스의 calc 메소드 실행!
		double result = calculable.calc(10, 4);
		System.out.println("결과: " + result);
	}
}
package ch16.sec05.exam01;

public class Computer {
	// 정적 기본타입 메소드
	public static double staticMethod(double x, double y) {
		return x + y;
	}
	
	// 인스턴스 기본타입 메소드
	public double instanceMethod(double x, double y) {
		return x * y;
	}
}
package ch16.sec05.exam01;

public class MethodReferenceExample {
	public static void main(String[] args) {
		Person person = new Person();
		
		// 정적 메소드 선언을 람다식으로 표현
		person.action((x, y) -> Computer.staticMethod(x,y));
		
		// 메소드 참조 형태
		// 위의 코드에서 람다식의 인자와 메소드의 매개변수가 같을 때
		// 인자를 생략해서 표현이 가능함!!
		// 정적 메소드일 때는 객체 생성이 필요없으므로 해당 클래스명 :: 메소드명 형태로 기입
		person.action(Computer :: staticMethod);
		
		// 인스턴스 메소드 호출을 위해 해당 객체를 생성
		Computer com = new Computer();
		
		// 인스턴스 메소드 선언을 람다식으로 표현
		person.action((x, y) -> com.instanceMethod(x, y));
		
		// 메소드 참조 형태
		// 람다식의 인자와 메소드의 매개변수가 같을 때 인자 생략 가능!
		// 인스턴스 메소드는 생성한 객체의 참조변수를 통해 불러올 수 있기 때문에
		// 참조변수 :: 인스턴스 메소드명의 형태!
		person.action(com :: instanceMethod);
	}
}

매개변수의 메소드 참조

  • 람다식에서 제공되는 a 매개변수의 메소드를 호출해서 b 매개변수를 매개값으로 사용함
  • 아래 코드의 경우 람다식의 인자가 모두 사용되었으므로, 메소드 참조를 통해 매개변수를 모두 생략 가능함
// 구조
(a, b) -> { a.instanceMethod(b); }
  • a의 클래스 이름 뒤에 :: 기호를 붙이고 메소드 이름을 기술함
package ch16.sec05.exam02;

@FunctionalInterface
public interface Comparable {
	int compare(String a, String b);
}
package ch16.sec05.exam02;

public class Person {
	public void ordering(Comparable comparable) {
		String a = "홍길동";
		String b = "김길동";

		int result = comparable.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.sec05.exam02;

// 소유권은 본인 거
public class MethodReferenceExample {
	public static void main(String[] args) {
		Person person = new Person();
		
		// a의 멤버함수가 b를 매개변수로 가지는 람다식
		// 인자가 모두 사용되었을 경우 인자를 모두 생략이 가능
		person.ordering((a,b) -> a.compareToIgnoreCase(b));
		// compareToIgnoreCase : 대소문자 구분하지 않음
		
		
		// compareTo : 두 인자의 크기 비교 여부를 확인
		// compareToIgnoreCase : 대소문자 구분하지 않고 두 인자의 크기 비교 여부를 확인
		
		
		// 참조 메소드 해석을 위해서는 해당 메소드로 이동해보기
		// 위의 구문과 동일함
		person.ordering(String :: compareToIgnoreCase);
	}
}

생성자 참조

  • 객체를 생성하는 것

  • 람다식이 단순히 객체를 생성하고 리턴하도록 구성하면 람다식을 생성자 참조로 대치가 가능함

  • 구조 : 클래스명 :: new

  • 생성자가 오버로딩 되어 여러 개가 있을 경우
    -> 컴파일러는 함수형 인터페이스의 추상 메소드와 동일한 매개변수 타입과 개수를 가지고 있는 생성자를 찾아 실행함
    -> 해당 생성자가 존재하지 않으면 컴파일 오류 발생

package ch16.sec05.exam03;

@FunctionalInterface
public interface Creatable1 {
	public Member create(String id);
}
package ch16.sec05.exam03;

@FunctionalInterface
public interface Creatable2 {
	public Member create(String id, String name);
}
package ch16.sec05.exam03;

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.sec05.exam03;

public class Person {
	public Member getMember1(Creatable1 creatable) {
		// 인터페이스 등록 및 Creatable1 객체 사용
		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.sec05.exam03;

public class ConstructorReferenceExample {
	public static void main(String[] args) {
		Person person = new Person();

		// id 인자를 사용하여 Member 객체를 생성하여 리턴한다
		Member m11 = person.getMember1(id -> new Member(id));

		// 위의 코드와 같음
		// 인자가 동일하기 때문에 생략이 가능
		Member m1 = person.getMember1( Member :: new );
		System.out.println(m1);
		System.out.println();
		
		Member m21 = person.getMember2((id, name) -> new Member(id, name));

		// id 인자와 name 인자를 사용하여 Member 객체를 생성하여 리턴함
		Member m2 = person.getMember2( Member :: new );
		System.out.println(m2);
	}
}
profile
끄작끄작

0개의 댓글