int add(int numA, int numB) { return numA + numB; }
//이것을
(numA,numB)-> numA+numB;
자바는 람다식을 익명 구현 객체로 변환한다. 익명 구현 객체란 익명객체에서 설명한 것과 같이 이름이 없는 인터페이스 구현 객체를 말한다. 예를 들어 다음과 같이 Calculable 인터페이스가 있다고 가정해보자
public interface Calculable{
//추상 메소드
void calculate(int x, int y);
}
Calculable 인터페이스의 익명 구현 객체는 다음과 같이 생성 할 수 있다.
new Calcuable() {
@Override
public void calculate(int x, int y) { 처리내용 }
}
이것을 람다식으로 표현하면 다음과 같다. 추상 메소드인 calculate( )는 두 개의 매개변수를 가지므로 (x, y)로 표현되었고, 화살표 -> 뒤에 calculate( )의 실행 블록이 온다.
(x, y) -> {처리 내용};
람다식은 인터페이스의 익명 구현 객체이므로 인터페이스 타입의 매개변수에 대입될 수 있다. 예를 들어 다음과 같이 Calculable 매개변수를 가지고있는 action() 메소드가 있다고 가정해보자.
public void action(Calculable calculable) {
int x = 10;
int y = 4;
calculable.calculate(x, y); //데이터를 제공하고 추상 메소드를 호출
}
action( )메소드를 호출할 때 매개값으로 다음과 같이 람다식을 제공할 수 있다. action( )메소드에서 calculable.calculate(x, y)를 실행하면 람다식의 중괄호 블록이 실행되면서 데이터가 처리된다. 여기에서 action( )메소드는 제공된 람다식을 이용해서 내부 데이터를 처리하는 처리부 역할을 한다.
action( (x, y) -> {
int result = x + y;
System.out.println(result);
});
다음 그림과 같이 람다식1과 람다식2 중에서 어떤 람다식을 매개값으로 제공하느냐에 따라 결산 결과는 달라질 수 있다.

인터페이스의 익명 구현 객체를 람다식으로 표현하려면 인터페이스가 단 하나의 추상 메소드만 가져야 한다. 따라서 다음과 같이 두 개 이상의 추상 메소드를 가진 RemoteControl 인터페이스는 람다식으로 표현할 수 없다.
public interface RemoteControl {
void turnOn();
void turnOff();
}
인터페이스가 단 하나의 추상 메소드를 가질 때, 이를 함수형 인터페이스라고 한다. 다음 인터페이스들은 함수형 인터페이스이다.

인터페이스가 함수형 인터페이스임을 보장하기 위해서는 @FunctionalInterface 어노테이션을 붙이면 된다. @FunctionalInterface를 붙이는 것은 선택사항이지만, 컴파일 과정에서 추상 메서드가 하나인지 검사하기 때문에 정확한 함수형 인터페이스를 작성할 수 있게 도와주는 역할을 한다.
Calculable 인터페이스를 다음과 같이 작성해보자.
//Calculable.java
package ch16.lambda02.sec01;
@FunctionalInterface
public interface Calculable {
//추상 메소드
void calculate(int x, int y);
}
//LambdaExample.java
package ch16.lambda02.sec01;
public class LambdaExample {
public static void main(String[] args) {
action((x, y) -> {
int result = x + y;
System.out.println("result : "+result);
});
action((x, y) -> {
int result = x - y;
System.out.println("result : "+result);
});
}
public static void action(Calculable calculable) {
//데이터
int x = 10;
int y = 4;
// 데이터 처리
calculable.calculate(x, y);
}
}
실행결과
result : 14
result : 6
함수형 인터페이스의 추상 메소드에 매개변수가 없을 경우 람다식은 다음과 같이 작성할 수 있다. 실행문이 두 개 이상일 경우에는 중괄호를 생략할 수 없고, 하나일 경우에만 생략할 수 있다.
1.
() -> {
실행문;
실행문;
}
2.
() -> 실행문;
함수형 인터페이스의 추상 메소드에 매개변수가 있을 경우 람다식은 다음과 같이 작성할 수 있다. 매개변수를 선언할 때 타입은 생략할 수 있고, 구체적인 타입 대신에 var를 사용할 수도 있다. 하지만 타입을 생략하고 작성하는 것이 일반적이다.
1.
(타입 매개변수, ...) -> { 실행문; 실행문; }
ㄴ> (타입 매개변수, ...) -> 실행문;
2.
(var 매개변수, ...) -> { 실행문; 실행문;}
ㄴ> (var 매개변수, ...) -> 실행문;
3.
(매개변수, ...) -> { 실행문; 실행문; }
ㄴ> (매개변수, ...) -> 실행문;
매개변수가 하나일 경우에는 괄호를 생략할 수도 있다. 이때는 타입또는 var를 붙일 수 없다.
1.
매개변수 -> {
실행문;
실행문;
}
2.
매개변수 -> 실행문;
함수형 인터페이스의 추상 메소드에 리턴값이 있을 경우 람다식은 다음과 같이 작성할 수 있다.
return 문 하나만 있을 경우에는 중괄호와 함께 return 키워드를 생략할 수 있다. 리턴값은 연산식 또는 리턴값이 있는 메소드 호출로 대체할 수 있다.
1.
(매개변수, ...) -> {
실행문;
return 값;
}
2.
(매개변수, ...) -> return 값; // return을 생략할 수 있다.
(매개변수, ...) -> 값
메소드 참조는 말 그대로 메소드를 참조해서 매개변수의 정보 및 리턴 타입을 알아내 람다식에서 불필요한 매개변수를 제거하는 것을 목적으로 한다. 예를 들어 두 개의 값을 받아 큰 수를 리턴하는 Math 클래스 max( ) 정적 메소드를 호출하는 람다식은 다음과 같다.
(left, right) -> Math.max(left, right);
람다식은 단순히 두 개의 값을 Math.max( ) 메소드의 매개값으로 전달하는 역할만 하기 때문에 다소 불편해 보인다. 이 경우에는 다음과 같이 메소드 참조를 이용하면 매우 깔끔하게 처리할 수 있다.
//메소드 참조를 이용했을 때
Math :: max;
정적 static 메소드를 참조할 경우에는 클래스 이름 뒤에 :: 기호를 붙이고 정적 메소드 이름을 기술한다.
클래스 :: 메소드
인스턴스 메소드일 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메소드 이름을 기술한다.
참초변수 :: 메소드
예제를 살펴보자
@FunctionalInterface
public interface Calcuable{
double calc(double x, double y);
}
public class Person{
public void action(Calcuable calcuable) {
double result = calcuable.calc(10,4);
System.out.println("result : "+ result);
}
}
public class Computer{
//정적 메소드
public static double staticMethod(double x, double y) return x + y;
//인스턴스 메소드
public double instanceMethod(double x, double y) return x * y;
}
main(String[] args){
Person person = new Person();
//정적 메소드일 경우 / 람다 메소드 참조
person.action( computer :: staticMethod );
//인스턴스 메소드일 경우 / 람다 메소드 참조
Computer com = new Computer();
person.action( com :: instanceMethod );
}
실행결과
result : 14.0
result : 40.0
다음과 같이 람다식에서 제공되는 a 매개변수의 메소드를 호출해서 b 매개변수를 매개값으로 사용하는 경우도 있다.
(a, b) -> { a.instanceMethod(b); }
이것을 메소드 참조로 표현하면 다음과 같다. a의 클래스 이름 뒤에 :: 기호를 붙이고 메소드 이름을 기술한다. 작성 방법은 정적 메소드 참조와 동일하지만, a의 인스턴스 메소드가 사용된다는 점에서 다르다.
클래스 :: instanceMethod
예제를 살펴보자
public interface Comparable {
int compare(String a,String b);
}
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+"보다 뒤에 옵니다.");
}
}
}
public class MethodReferenceExample {
public static void main(String[] args) {
Person person = new Person();
person.ordering(String :: compareToIgnoreCase);
// String 메소드참조
}
}
실행결과
홍길동은 김길동보다 뒤에 옵니다.
생성자 참조한다는 것은 객체를 생성하는 것을 의미한다. 람다식이 단순히 객체를 생성하고 리턴하도록 구성된다면 람다식을 생성자 참조로 대치할 수 있다. 다음 코드를 보면 람다식은 단순히 객체를 생성한 후 return만 한다.
(a, b) -> { return new 클래스(a, b); }
이것을 생성자 참조로 표현하면 다음과 같다. 클래스 이름 뒤에 :: 기호를 붙이고 new 연산자를 기술하면 된다.
클래스 :: new
생성자가 오버로딩되어 여러 개가 있을 경우, 컴파일러는 함수형 인터페이스의 추상 메소드와 동일한 매개변수 타입과 개수를 가지고 있는 생성자를 찾아 실행한다. 만약 해당 생성자가 존재하지 않으면 컴파일 오류가 발생한다.
어려우니 예제를 참고하자
//Createble1.java
@FunctionalInterface
public interface Createble1 {
public Member create(String id);
}
//Createble2.java
@FunctionalInterface
public interface Createble2 {
public Member create(String id, String name);
}
매개변수가 다른 interface를 2개 생성
//Member.java
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;
}
}
id로 객체를 생성하는 생성자, id와 name으로 객체를 생성하는 생성자 작성
//Person.java
public class Person {
public Member getMember1(Createble1 createble) {
String id = "winter";
Member member = createble.create(id);
return member;
}
public Member getMember2(Createble2 createble) {
String id = "winter";
String name = "한겨울";
Member member = createble.create(id,name);
return member;
}
}
Person 클래스에 메소드2개를 작성
//ConstructorReferenceExample.java
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);
}
}
실행결과
Member(String id)
{ id:winter, name : null }
Member(String id, String name)
{ id:winter, name : 한겨울 }
확인 문제
1. 람다식에 대한 설명으로 틀린 것은 무엇?
- 람다식은 함수형 인터페이스의 익명 구현 객체를 생성한다.
- 매개변수가 없을 경우 ( ) -> {...} 형태로 작성한다.
- (x, y) -> { return x+y; }는 (x,y)->x+y로 바꿀 수 있다.
- @FunctionalInterface가 기술된 인터페이스만 람다식으로 표현이 가능하다.
2. 메소드 참조와 생성자 참조에 대한 설명으로 틀린 것은 무엇?
- 메소드 참조는 함수적 인터페이스의 익명 구현 객체를 생성한다.
- 인스턴스 메소드는 "참조변수::메소드"로 기술한다.
- 정적 메소드는 "클래스::메소드"로 기술한다.
- 생성자 참조인 "클래스::new"는 매개변수가 없는 디폴트 생성자만 호출한다.
3. 다음 중 잘못 작성된 람다식은 무엇?
- a->a+3
- a,b -> a*b
- x -> System.out.println(x/5)
- (x,y)-> Math.max(x,y)
4. 다음 코드의 실행 결과를 보고 빈 곳에 들어갈 람다식을 작성public class Example{ public static void main(String[] args){ Thread thread = new Thread( //??? ); thread.start(); } }실행결과 작업 스레드가 실행됩니다. 작업 스레드가 실행됩니다. 작업 스레드가 실행됩니다.
5. 다음 코드의 실행 결과를 보고 ? 친 곳에 들어갈 람다식을 작성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(); } }public class Example { public static void main(String[] args) { Button btnOk = new Button(); btnOk.setClickListener( ? ); btnOk.click(); // Button btnCancel = new Button(); btnCancel.setClickListener( ? ); btnCancel.click(); } }실행결과 Ok 버튼을 클릭했습니다. Cancel 버튼을 클릭했습니다.
6. 다음 코드를 보고 Function 함수형 인터페이스 작성
public class Example { public static double calc(Function fun) { double x = 10; double y = 4; return fun.apply(x, y); } // // public static void main(String[] args) { double result = calc( (x, y) -> (x / y) ); System.out.println("result : " + result); } }실행결과 result : 2.5
7. 다음은 배열 항목 중에 최대값 또는 최소값을 찾는 코드이다. maxOrMin()메소드를 호출할 때 빈 곳에 람다식 작성
@FunctionalInterface public interface Operator { public int apply(int x, int y); }
public class Example { private static int[] scores = {10, 50, 3 }; // public static int maxOrMin(Operator operator) { int result = scores[0]; for(int score : scores) { result = operator.apply(result, score); } return result; } public static void main(String[] args) { //최댓값 얻기 int max = maxOrMin( ? ); System.out.println("최대값: "+max); // //최소값 얻기 int min = maxOrMin( ? ); System.out.println("최소값: "+min); } }실행결과 최대값: 50 최소값: 38. 다음은 학생의 영어 평균 점수와 수학 평균 점수를 계산하는 코드입니다. 빈 곳에 avg( ) 메소드를 작성
@FunctionalInterface public interface Function<T> { public double apply(T t); }
public class Student { private String name; private int englishScore; private int mathScore; // public Student(String name, int englishScore, int mathScore) { this.name = name; this.englishScore = englishScore; this.mathScore = mathScore; } // public String getName() { return name; } public int getEnglishScore() {return englishScore; } public int getMathScore() {return mathScore; } }
public class Example { private static Student[] students = { new Student("홍길동", 90, 96), new Student("신용권", 95, 93) }; //avg() 메소드 작성 ? // public static void main(String[] args) { double englishAvg = avg( s -> s.getEnglishScore() ); System.out.println("영어 평균 점수 : " + englishAvg ); // double mathAvg = avg( s -> s.getMathScore() ); System.out.println("수학 평균 점수 : " + mathAvg ); } }9. 8번 문제에서 Example 클래스의 main( ) 메소드를 실행할 때, avg( )메소드의 매개값으로 람다식을 사용하지 않고 메소드 참조로 변경
double englishAvg = avg( s -> s.getEnglishScore() ); -> double englishAvg = avg( ? ); // double mathAvg = avg( s -> s.getMathScore() ); -> double mathAvg = avg( ? );