자바/자바 입문 공부일지

자바 기초 공부 일지 47. 람다와 함수형 인터페이스

Tomitom 2022. 11. 4. 10:44
반응형

 

 

함수란 재사용할 수 있는 코드 조각이며 이것이 클래스 안에 포함되어 있으면 메소드라고 합니다. 

함수형 인터페이스란 함수를 정의하기 위한 인터페이스 입니다. (추상메소드를 하나만 가지고 있는 인터페이스) 

함수형 인터페이스는 객체 뿐만 아니라 기능적인 상호작용을 가능하게 합니다.

 

람다를 사용하는 경우들을 살펴볼게요. 

 

1. 인스턴스보다 기능 하나가 필요한 상황을 위한 람다에서 사용합니다. 

함수를 외부에 작성한 뒤 인자로 넣는 것이 아니라, 인자의 자리에 직접적으로 람다를 넣을 수 있습니다. 

 

아래의 예제를 살펴볼게요. 

리스트의 정렬sort를 위해 sort의 기준이 되는 오버라이딩을 클래스로 정의했습니다.

 

class StrCmp implements Comparator<String>{

    @Override
    public int compare(String o1, String o2) {
        return o1.length() - o2.length(); // 길이 순서에 따른 정렬을 위한 것
    }
   
   
}
그리고 sort의 기준이 되는 클래스의 메소드를 list 의 기준으로 넣어줄게요. 
 
   Collections.sort(list, new StrCmp());   // 문자열의 길이 순서에 따른 정렬  
 
이렇게 작성된 예시는 다음과 같습니다. 
 
 
package day20;

import java.util.*;


class StrCmp implements Comparator<String>{
// Comparator 는 비교대상 두 개를 가지고 판단. 
// int compare(T o1, T o2); 이 메소드를 통해 두 객체의 특정 값을 연산해서 
// 음수가 나오면 o1의 객체가 작다고 판단하며, 양수가 나오면 o2의 객체가 작다고 판단
// 양수는 오름차순, 음수는 내림차순 

	@Override
	public int compare(String o1, String o2) {
		return o1.length() - o2.length(); // 길이 순서에 따른 정렬을 위한 것 
	}
	
	
}
public class Practice01 {

	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("Robot");
		list.add("Lambda");
		list.add("Box");
		
		Collections.sort(list, new StrCmp());	// 문자열의 길이 순서에 따른 정렬  
		for(String s : list) {System.out.println(s);} // 리스트를 출력하기 위한 for each 구문
		
	}
}

 

 

추상메소드를 하나만 가지고 있는 인터페이스를 별개로 구현하여 대입하는 번거로움을 줄이기 위해 

같은 예제를 람다식을 사용해서 작성해볼게요. 

 

상기 코드에서 이미 Collection.sort 를 사용하고 있으므로 자바는 자동적으로 그 뒤의 객체를 인식하게 됩니다. 

Collections.sort(list, comparator); 이런 형태의 정렬 방법은

앞에는 기존대로 컬렉션 객체가 들어가고, 뒤에 Comparator 객체가 들어가게 됩니다.

그러면 정렬시 list의 기준이 아니라 새롭게 매개변수값으로 받은 Comparator 객체를 기준으로 해서 정렬을 실행하게 되기 때문에 Comparable는 나의 compareTo() 메소드에 상대를 넣어서 비교를 했다면, ​Comparator는 비교대상 2개를 가지고 판단을 하게 되는 것입니다.

 

int compare(T o1, T o2); 이 메소드를 통해 두 객체의 특정 값을 연산해서 음수가 나오면 o1의 객체가 작다고 판단하며, 양수가 나오면 o2의 객체가 작다고 판단을 하게 되므로, 자바에서 자동적으로 인식한 객체에 따라 

 람다는 Collections sort(list,     <-의 뒤에 두 개의 o1, o2 객체를 넣음으로써 comparatot 의 객체를 인식하게 됩니다.

아래는 상기 내용을 람다식으로 표현한 것입니다. 

 

Collections.sort(list,(String o1, String o2)  // String o1과 String o2 의 객체를 인식합니다. 

        ->{return o1.length() - o2.length(); }   // 리스트의 정렬 순서를 확인하여 정렬합니다. 
            );  
 
 
작성된 코드를 확인하고 비교해볼게요.
 
 
package day20;

import java.util.*;

public class Practice01 {

	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("Robot");
		list.add("Lambda");
		list.add("Box");
		
		Collections.sort(list,(String o1, String o2) 
		->{return o1.length() - o2.length(); }
			);	  
		for(String s : list) {System.out.println(s);} 
	}
}

훨씬 간결하게 코드가 작성된 것을 확인할 수 있습니다. 

 


 

매개변수가 하나이고 간결한 람다 식은 조금 더 축약해서 사용할 수 있습니다. 

아래 예시와 함께 어떤 경우에 무엇이 생략되는지 확인해볼게요. 

 

interface Printable {   //함수형 인터페이스
    void print(String s);  // 매개변수 하나, 반환형 void
 }
 
 class OneParamNoReturn {
    public static void main(String[] args) {
       Printable p;
       
       // ↓람다의 전체 식  
       p = (String s) -> { System.out.println(s); };    // 줄임 없는 표현
       p.print("Lambda exp one.");
 
       // ↓ 만약에 람다의 몸체 안에 명령문이 하나일 경우에는 중괄호가 생략 됩니다.
       p = (String s) -> System.out.println(s);    // 중괄호 생략
       p.print("Lambda exp two.");

       // ↓ 매개 변수 형은 생략할 수 있습니다.
       p = (s) -> System.out.println(s);    // 매개변수 형 생략
       p.print("Lambda exp three.");

       // ↓ 매개변수가 하나인 경우에만 소괄호를 생략할 수 있습니다.
       p = s -> System.out.println(s);    // 매개변수 소괄호 생략
       p.print("Lambda exp four.");
    }
 } 
 
 

 
return 문을 반환하는 람다식의 축약을 살펴볼게요. 
 
 
interface Calculate {
    int cal(int a, int b); // 값을 반환하는 추상 메소드
    //void 처럼 아무것도 반환하지 않는 것이 아니라 값을 반환하게 된다면 retrun 을 사용해야 합니다.
 }
 
 class TwoParamAndReturn {
    public static void main(String[] args) {
       Calculate c;
       
       // ↓ 다중의 매개변수가 있고 반환의 리턴의 식을 작성할 때에는 retrun 문의 중괄호를 생략할 수 없습니다.
       c = (a, b) -> { return a + b; };
       System.out.println(c.cal(4, 3));
 
       // ↓ 그러나 연산 결과가 남으면, 별도로 명시하지 않아도 반환 대상이 됩니다.
       c = (a, b) -> a + b;
       System.out.println(c.cal(4, 3));
    }
 }
 
다음의 예제를 보며 어떻게 축약하는지 한 번 더 확인해볼게요.
getMeow() 메소드를 사용하면 야옹이라고 우는 코드를 짜볼게요.
 
 
 
package day20;

@FunctionalInterface

interface Cat{
	String getMeow(); 
}

public class Lambda01 {
	public static void main(String[] args) {
		
	
		Cat cat = () -> "애옹";
		System.out.println(cat.getMeow());
		}
		
}

코드가 무척 간단해졌어요.  

 


 

 
 함수형 인터페이스인지 확인할 수 있는 명령어가 있습니다. 
 
@FunctionalInterface // 함수형 인터페이스의 조건을 갖추었는지에 대한 검사를 컴파일러에게 요청합니다. 
추상 메소드가 딱 하나만 있어야 함수형 인터페이스입니다. 
함수형 인터페이스가 아닐 때 오류가 발생합니다.
 
package day20;

@FunctionalInterface // 함수형 인터페이스의 조건을 갖추었는지에 대한 검사를 컴파일러에게 요청!
// 추상 메소드가 딱 하나만 있어야 함수형 인터페이스입니다. 
// 함수형 인터페이스가 아닐 때 오류가 발생합니다. 
interface MixCoffee{
	void drinkCoffee(int time); 
	
}


public class Lambda01 {

	public static void main(String[] args) {
		
		// 생략없는 완성형으로 구현해봅니다.  
		
		MixCoffee mc = (int time) -> { 
			System.out.println(time + "모금 마셨다.");
		};
		
		// 가능한 모든 생략을 해봅니다. 
		
		MixCoffee mc2 = time -> System.out.println(time + "모금 마셨다.");
		
		// 람다는 그 자체로 하나의 메소드가 아닙니다.
		// . 찍고 호출을 해야합니다. 
		
		mc.drinkCoffee(4);

	};

}

제네릭 기반의 인터페이스도 같이 확인해보겠습니다. 

타입 인자가 전달이 되면 추상 메소드 T는 결정이 됩니다. 

예제를 통해서 확인해보겠습니다. 

 

package day20;

@FunctionalInterface
interface Sum<T>{
	T sum(T n1, T n2); 
}

public class Lambda01 {
	public static void main(String[] args) {		

		Sum<Integer> s1 = (n1, n2) -> n1 + n2;
		Sum<Double> s2 = (n1, n2) -> n1 + n2; 
		
		System.out.println(s1.sum(3, 5));
		System.out.println(s2.sum(3.1, 5.7));
		
	}
		
}

 

즉 제네릭은 인스턴스 더하기 인스턴스가 되므로

<Integer> 을 입력함으로써 기본 자료형을 정수형으로 박싱할 수 있습니다. 

 

예제문제를 통해서 조금 더 살펴볼게요. 

제네릭 기반의 다음 코드가 있습니다.

calAndShow 메소드를 람다 기반으로 호출해서 5+8 을 계산 후 출력하겠습니다. 

 

package day20;

@FunctionalInterface
interface Calculate<T>{
	T cal(T a, T b);
}

public class Quiz01 {
	public static <T> void calAndShow(Calculate<T> op, T n1, T n2) {
		T r = op.cal(n1,  n2); 
		System.out.println(r);
	}

	public static void main(String[] args) {


	}
}

 

아래는 위 예제문제의 예시 답안입니다. 

더보기

 

호출식은 다음과 같습니다. 

 

Quiz01.<Integer>calAndShow((n1, n2) -> n1 + n2, 5, 7);

 

package day20;

@FunctionalInterface
interface Calculate<T>{
	T cal(T a, T b);
}

public class Quiz01 {
	public static <T> void calAndShow(Calculate<T> op, T n1, T n2) {
		T r = op.cal(n1,  n2);
		System.out.println(r);
	}

	public static void main(String[] args) {
		
		Quiz01.<Integer>calAndShow((n1, n2) -> n1 + n2, 5, 7);
	}
}

 

반응형