본문 바로가기
Work/Java

[Java] Lambda 람다식 (함수형 인터페이스와 메소드참조) - 총정리

 

1. 람다 표현식(lambda expression)이란?

람다 표현식(lambda expression)이란  "선언없이 실행가능한 함수" 입니다.

간단히 말해  코드한줄에 함수를 써서 그것을 호출하는 방식으로 표현한 것입니다.

자바 8부터 람다식(Lambda Expressions)을 지원하며,

간결한 코딩만큼 가독성 면에서 장점을 가진다.

 

 

2. 람다식 장단점

 

장점

1. 불필요한 코드를 제거하여 간결하게 만들 수 있습니다.

2. 코드가 간결하고 식에 개발자의 의도가 명확히 드러나므로 가독성이 향상됩니다.

3. 함수를 만드는 과정없이 한번에 처리할 수 있기에 코딩시간이 단축됩니다.

4. 다중 cpu를 활용하는 형태로 구현되어 병렬 처리에 유리합니다.

단점

1. 람다를 사용하면서 만드는 무명함수는 재사용이 불가능합니다.

2. 디버깅 시 함수 콜 스택 추적이 다소 어려움.

3. 재귀로 만들어 완전탐색하는 경우, 느릴수 있다.

4. 한 클래스내에 많은 람다식을 사용하는 경우, 오히려 가독성이 떨어진다.

 

 

3. 람다식 작성법

 

1. 자바에서는 화살표(->) 기호를 사용하여 매개변수부와 선언부를 나눕니다.

2. 람다식 사용을 위해서는, 함수형 인터페이스에 접근하여 사용됩니다.

함수형 인터페이스란 한개의 추상메소드를 가지는 인터페이스로, 동적인 함수구현후 사용하기 위해 정의합니다. 

함수형 인터페이스 선언

@FunctionalInterface	// @FunctionalInterface 어노테이션을 반드시 명시하여 정의함
interface Calc {	// 함수형 인터페이스의 선언

    public int min(int x, int y);

}

함수형 인터페이스에 접근하여 람다식 사용

public class LambdaExam1 {

public static void main(String[] args){

        Calc minNum = (x, y) -> x < y ? x : y; // 추상 메소드의 구현

        System.out.println(minNum.min(3, 4));  // 함수형 인터페이스의 사용

    }

}

람다식 사용을 위해 매번 함수형인터페이스를 정의하는건 말도 안되는 일이다. 오히려 간결하지 못하니깐!

자바에서 기본으로 제공되는 함수형 인터페이스를 알고 활용해야 가치가 있다. (아래에서 해당내용을 추가로 다룸)

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

 

java.util.function (Java Platform SE 8 )

Interface Summary  Interface Description BiConsumer Represents an operation that accepts two input arguments and returns no result. BiFunction Represents a function that accepts two arguments and produces a result. BinaryOperator Represents an operation u

docs.oracle.com

 

3. 매개변수의 타입을 추론할 수 있는 경우에는 타입을 생략할 수 있습니다.

4. 매개변수 작성부에서  변수가 하나인 경우에는 괄호(())를 생략할 수 있습니다.

5. 함수의 몸체가 하나의 명령문만으로 이루어진 경우에는 중괄호({})를 생략할 수 있습니다. (이때 세미콜론(;)은 붙이지 않음)

6. 함수의 몸체에 return 문이 있는 경우에는 중괄호({})를 생략할 수 없습니다.

7. return 문 대신 표현식을 사용할 수 있으며, 이때 반환값은 표현식의 결괏값이 됩니다. (이때 세미콜론(;)은 붙이지 않음)

 

 

4. 람다식 활용 예제

 

람다를 그래서 어디에 붙여서, 어떻게 활용할 수 있는지가 제일 중요하다.

사실 제일 많이 활용되는건 sort를 위해 함수형 인터페이스 Comparatorcompare를 표현할 때이다.

그 외에 상황별로 사용할 수 있는 기본적인 함수형 인터페이스는 아래와 같다.

 

1) 기본 함수형 인터페이스 (파라미터가 없거나 하나임) 

함수형 인터페이스 메서드 매개변수 반환값
java.lang.Runnable void run() X X
Supplier<T> T get() X O
Consumer<T> void accept(T t) O X
Function<T, R> R apply(T t) O O
Predicate<T> boolean test(T t) O O

(T: 데이터 타입, R: 리턴 타입 - 참조URL)

위에 명시한 함수형 인터페이스를 활용하여 람다식을 정의하고 사용해보자.

 

① Comparator 인터페이스의 compare()

사용자 정렬을 구현하는 부분에서 메소드 오버라이딩을 람다식으로 간단히 진행할수 있다.

// 프로그래머스 - 고득점KIT > 정렬 > 가장 큰 수 문제풀이

public String solution(int[] numbers){
    StringBuffer sb = new StringBuffer();  
        
    String[] str = new String[numbers.length];       
    for(int i=0;i<numbers.length; i++)
      str[i] = Integer.toString(numbers[i]);
            
    // 핵심부분 lamda식으로 두개를 가져와 compareTo()로 비교 정렬
    Arrays.sort(str, (a,b)->{
      return -(a+b).compareTo(b+a);
    });

    if(str[0].equals("0"))
      return "0";
    else {
      for(String s : str) {
        //System.out.println(s);
        sb.append(s);
      }
      return sb.toString();
    }
}

Arrays. Sort시 두번째 파라미터는 Comparator 인터페이스의 구현이 들어가는 부분이다.

해당 부분에서 compare 메소드를 원래 아래 코드와 같이 함수 오버라이딩 해야하지만,

위에서와 같이 람다식을 쓰면 간단해진다.

// 람다식을 지원하지않던 java 1.8이전에 구현 코드
public String solution(int[] numbers){
    StringBuffer sb = new StringBuffer();  
        
    String[] str = new String[numbers.length];       
      for(int i=0;i<numbers.length; i++)
        str[i] = Integer.toString(numbers[i]);
            
        Arrays.sort(str, new Comparator<String>() {
          @Override
          public int compare(String o1, String o2) {  
            return -Integer.compare(Integer.parseInt(o1+o2),Integer.parseInt(o2+o1));  // 여기만 다름
            }
        });
        
        if(str[0].equals("0"))
          return "0";
        else {
          for(String s : str) {
            //System.out.println(s);
            sb.append(s);
          }
          return sb.toString();
        }

 

② Runnable 인터페이스의 run() 

Runnable 인터페이스는 매개변수도 없고, 반환값도 없는 run() 메소드를 람다식으로 활용할 수 있다.

package Lambda;

import java.util.function.Consumer;

public class Lambda_Runnable {
	public static void main(String[] args) {
		
		//Runnable 객체 생성시 정의
		Runnable runnable = () -> System.out.println("1. Runnable run lambda 람다식 정의");
		runnable.run();
		
		Thread t1 = new Thread(runnable);
		t1.start();
		
		// Runnable 객체가 파라미터로 들어가는 부분에서 정의
		Thread t2 = new Thread(() -> System.out.println("2. Runnable run lambda 람다식 정의"));
		t2.start();
	}	
}

한번은 미리 Runnable의 객체 생성시 람다식으로 정의하였고,

한번은 Thread 객체 생성시 파라미터에 들어갈 Runnable 객체전달 부분에 람다식으로 정의하여 전달하였다.

 

이를 통해 Thread start()가 수행될때 불러지는 run을 재정의하여, 로그를 기록하는데 활용할 수 있겠다. 

 

③ Supplier인터페이스의 get() 

매개변수가 없이, 파라미터를 반환하는 Supplier인터페이스는 정의된 값을 반환해주므로 Supplier이다.

import java.util.function.*;

public class ETC {
	public static void main(String[] args) {
		
		// Supplier 
		Supplier<String> supplier = () -> "String";
		System.out.println(supplier.get()); // String
	}
}

 

④ Consumer인터페이스의 accept( T ) 

매개변수는 있지만, 소비해버리고 반환하지 않는 인터페이스이다. 

import java.util.function.*;

public class ETC {
	public static void main(String[] args) {			
		// Consumer
		Consumer<Integer> consumer = (num) -> System.out.println(num + 1);
		consumer.accept(1); // 2
	}
}

 

 

⑤. Function인터페이스의 apply( T ) 

매개변수도 있고, 반환값도 존재하는 일반적인 적용 함수.

import java.util.function.*;

public class ETC {
	public static void main(String[] args) {
		// Function
		Function<Integer, Integer> function = (num) -> num + 1;
		int result1 = function.apply(1);
		System.out.println(result1); // 2
        
       		// 메소드 참조 표현 - 아래에서 설명하겠음
		Function<String, Integer> function2 = String::length; 
		int len = function2.apply("Hello World");
		System.out.println(len);
	}
}

 

⑥ Predicate 인터페이스의 test( Boolean )

매개변수도 있고, 반환값도 존재하지만, Function과 달리 Boolean 값을 반환한다.

import java.util.function.*;

public class ETC {
	public static void main(String[] args) {
		// Predicate
		Predicate<Integer> predicate = (num) -> num > 0;
		boolean result2 = predicate.test(1);
		System.out.println(result2); // true
	}
}

 

 

2) 파라미터가 두개인 함수형 인터페이스

(T/U: 데이터 타입, R: 리턴 타입)

함수형 인터페이스 메서드 매개변수 반환값
BiConsumer<T, U> void accept(T t, U u) O O
BiFunction<T, U, R> R apply(T t, U u) O O
BiPredicate<T, U> boolean test(T t, U u) O O
// BiConsumer
BiConsumer<Integer, Integer> biConsumer = (num1, num2) -> System.out.println(num1 + num2);
biConsumer.accept(1, 2); // 3


// BiFunction
BiFunction<Integer, Integer, Integer> biFunction = (num1, num2) -> num1 + num2;
int result1 = biFunction.apply(1, 2);
System.out.println(result1); // 3


// BiPredicate
BiPredicate<Integer, Integer> biPredicate = (num1, num2) -> num1 > num2;
boolean result2 = biPredicate.test(1, 2);
System.out.println(result2); // false

 

3) 파라미터 타입이 반환타입과 일치하는 인터페이스

함수형 인터페이스 메서드 매개변수 반환값
UnaryOperator<T> T apply(T t) O O
BinaryOperator<T> T apply(T t, T t) O O
// UnaryOperator
UnaryOperator<Integer> unaryOperator = (num) -> num;
int result1 = unaryOperator.apply(1);
System.out.println(result1); // 1

// BinaryOperator
BinaryOperator<Integer> binaryOperator = (num1, num2) -> num1 + num2;
int result2 = binaryOperator.apply(1, 2);
System.out.println(result2); // 3

 

 

+ 메소드 참조

메소드 참조란 함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법이다.

일반 메소드를 참조하기 위해서는 다음의 3가지 조건을 만족해야 한다.

  • 함수형 인터페이스의 매개변수 타입 = 메소드의 매개변수 타입
  • 함수형 인터페이스의 매개변수 개수 = 메소드의 매개변수 개수
  • 함수형 인터페이스의 반환형 = 메소드의 반환형

참조가능한 메소드는 일반 메소드, Static 메소드, 생성자가 있으며 클래스이름 : : 메소드이름 으로 참조할 수 있다. 이렇게 참조를 하면 함수형 인터페이스로 반환이 된다.

메소드 참조는 람다식 만큼이나 깔끔하고 가독성 좋은 코드를 작성하는데 도움을 준다.

// 메소드 참조 비교 예제
DoubleUnaryOperator oper;

oper = (n) -> Math.abs(n); // 람다 표현식
System.out.println(oper.applyAsDouble(-5));

oper = Math::abs; // 메소드 참조
System.out.println(oper.applyAsDouble(-5));

 

메소드 참조표현

public class MethodRef {
	public static void main(String[] args) {
		
		// Consumer
		Consumer<Integer> consumer = System.out::println;
		consumer.accept(1); // 2
	
	
		// Function
		Function<Double, Double> function = Math::sqrt;
		System.out.println(function.apply(25.0)); // 2
	
	}
}

 

 

 

참고사이트

더보기

https://tcpschool.com/java/java_lambda_concept

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

https://coding-factory.tistory.com/265

 

[Java] 람다식(Lambda Expressions) -> 사용법 & 예제

 람다식이란? 람다식이란 "식별자없이 실행가능한 함수" 함수인데 함수를 따로 만들지 않고 코드한줄에 함수를 써서 그것을 호출하는 방식이라고 생각하시면 되겠습니다. 함수적 프로그래밍을

coding-factory.tistory.com

https://inma.tistory.com/151

 

[Java] 함수형 인터페이스 (Functional Interface)

지난 포스팅에 이어서 본 포스팅에서는 Java8에 이미 정의된 함수형 인터페이스에 대해 알아봅니다. 함수형 인터페이스를 개발자가 직접 만들어서 사용할 수 있지만 항상 새로운 함수형 인터페

inma.tistory.com

https://mangkyu.tistory.com/113

 

[Java] 람다식(Lambda Expression)과 함수형 인터페이스(Functional Interface) (2/5)

1. 람다식(Lambda Expression) 이란? Stream 연산들은 매개변수로 함수형 인터페이스(Functional Interface)를 받도록 되어있다. 그리고 람다식은 반환값으로 함수형 인터페이스를 반환하고 있다. 그렇기 때문

mangkyu.tistory.com

 

예제가 많은 사이트

https://codechacha.com/ko/java8-biconsumer-example/

 

Java 8 - BiConsumer 예제

BiConsumer는 2개의 인자를 받고 리턴 값이 없는 함수형 인터페이스입니다. BiConsumer는 Lambda 표현식으로 구현할 수 있습니다. accept() 호출과 함께 인자 2개를 전달하면 구현된 내용이 수행됩니다. Map.

codechacha.com