[함수형 프로그래밍] lambda, functional interface, method reference
Language/Java

[함수형 프로그래밍] lambda, functional interface, method reference

728x90
반응형

함수형 인터페이스

함수형 인터페이스는 말 그대로 Java의 interface이다.  
함수형 인터페이스는 단 하나의 추상 메서드만 가지고 있는 인터페이스이다.(예외 : 디폴트 메서드는 포함할 수 있음) 
함수형 인터페이스의 메서드를 람다 표현식으로 작성해서 다른 메서드의 파라미터로 전달할 수 있다.
즉, 람다 표현식 전체를 해당 함수형 인터페이스를 구현한 클래스의 인스턴스로 취급한다.
Java 8에서 새롭게 추가된 함수형 인터페이스 외에 기존에 작성되어 있는 하나의 추상 메서드만 가지고 있는 Java의 interface 또한 함수형 인터페이스로 취급할 수 있다.

 

자바에서 기본적으로 제공하는 함수형 인터페이스는 다음과 같은 것들이 있습니다.

  • Runnable
  • Supplier
  • Consumer
  • Function<T, R>
  • Predicate

이 외에도 다양한 것들이  java.util.function 패키지에 정의되어있습니다.

 


우리가 자주 쓰는 onClickListener를 예제를 들어 설명해보겠습니다.

 

우리에게 주어진것은 아래와 같은 함수가 하나 있는 인터페이스(함수형 인터페이스)입니다. 

이 인터페이스를 레거시코드, 익명 클래스, 람다식(함수형을 위한)으로 가져다 쓰는 코드를 각각 살펴보겠습니다.

    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

 

방법 1. 레거시(lagacy) code

class MyChildOnClickListener implements View.OnClickListener{

	@Override
	public void onClick(View v) {
		// !!내가 구현하구싶은 코드 !!
	}
}
MyChildOnClickListener listener1 = new MyChildOnClickListener();

button.setOnClickListener(listener1);

인터페이스를 상속받아서 자식 클래스(MyChildOnClickListerner)의

하나밖에 없는 추상 메서드(onClick())를 구현하고 

그 클래스의 인스턴스를 선언해서 사용한다. 

 

방법 2. 익명(Anonymous) 클래스 code

button.setOnClickListener(new Button.OnClickListener() {

	@Override
	public void onClick(View v) {
		// !!내가 구현하구싶은 코드 !!
	}
    
}) ;

인터페이스를 상속받아 클래스를 만들 때

이 클래스가 일회성으로 쓰이고, 그 클래스의 함수가 하나일 경우

익명 클래스로 만들어 사용한다. 

 

방법 3. 람다(lambda) code

button.setOnClickListener(v -> {
	// !!내가 구현하구싶은 코드 !!
}) ;

이렇게 만들어진 익명 클래스를 객체처럼(1급 시민) 사용할 때

람다식을 사용하여 축약해서 사용할 수 있다.

 

1급 시민
1. 변수에 담을 수 있다.
2. 인자로 전달할 수 있다.
3. 반환 값으로 전달할 수 있다.

람다

람다 표현식
람다 표현식은 함수형 인터페이스를 구현한 클래스 즉, 익명 클래스의 메서드를 단순화 한 표현식이다. 
함수형 인터페이스의 메서드를 람다 표현식으로 작성해서 다른 메서드의 파라미터로 전달할 수 있다.
즉, 람다 표현식 전체를 해당 함수형 인터페이스를 구현한 클래스의 인스턴스로 취급한다.

 

 

함수 디스크립터

함수형 인터페이스의 추상 메서드를 설명해놓은 시그니처를 함수 디스크립터(Function Descriptor)라고 한다.

java 8에서는 java.util.fuction 패키지로 다양한 새로운 함수형 인터페이스를 지원한다. 

 

함수 디스크립터의 Predicate 예제

public class FunctionalDescriptorPredicate {

    public static void main(String[] args) {
    
        List<Integer> numbers = Arrays.asList(1, 6, 10, 30, 65, 70, 102);
        List<Integer> result = filter(numbers, n -> n > 30);

        for(int number : result)
            System.out.println(number);
    }


    private static <T> List<T> filter(List<T> numbers, Predicate<T> p){
    
        List<T> result = new ArrayList<>();
        for(T number : numbers)
            if(p.test(number))
                result.add(number);

        return result;
    }
    
}

함수 디스크립터의 Consumer 예제

public class  FunctionalDescriptorConsumer {

    public static void main(String[] args) {
    
        List<Integer> numbers = Arrays.asList(1, 6, 10, 30, 65, 70, 102);
        forEachPrint(numbers, n -> System.out.println(n));
    }
    
	// accept() 구현
    public static <T> void forEachPrint(List<T> numbers, Consumer<T> c) {
    
        for(T number : numbers)
            c.accept(number);
    }
    
}

  함수 디스크립터의 Function 예제

public class FunctionalDescriptorFunction {

    public static void main(String[] args) {
    
        List<Character> characterList = Arrays.asList('a', 'b', 'c', 'd', 'e');
        List<Integer> asciiNumbers = map(characterList, character -> (int) character);

        for(int asciiNumber : asciiNumbers)
            System.out.println(asciiNumber);
    }
	
    // apply() 구현
    public static <T, R> List<R> map(List<T> list, Function<T, R> f){
    
        List<R> result = new ArrayList<>();
        for(T t : list)
            result.add(f.apply(t));
        return result;
    }
    
}

 

 

 

 


메서드 레퍼런스(method reference)

우리말로 번역하면 메서드 참조라는 의미입니다. 

람다 표현식 body(몸체) 부분에 기술되는 메서드를 이용해서 표현되며, 메서드의 이름만 전달합니다.
구분자( : : )를 붙이는 방식으로 메서드 레퍼런스를 표현합니다. 
메서드 레퍼런스를 사용하면 람다 표현식이 더욱 간결해집니다. 

(Car car -> car.getName())                      같다!!                  Car::getNage

 

Class Name::static method 메서드 레퍼런스 예

public static void main(String[] args) {

    // 축약전
    Function<String, Integer> f1 = (String s) -> Integer.parseInt(s);
    Integer result1 = f1.apply("3");

    // 중간단계
    Function<String, Integer> f2 = s -> Integer.parseInt(s);
    Integer result2 = f2.apply("3");

    // 람다 표현식을 메서드 레퍼런스로 축약
    Function<String, Integer> f3 = Integer::parseInt;
    Integer result3 = f3.apply("3");

}

 

Class Name::instance method 메서드 레퍼런스 예

public static void main(String[] args) {
    
    // 축약전
    Function<Car, String> f1 = car -> car.getCarName();
    String carName1 = f1.apply(new Car("제네시스"));

    // 람다 표현식을 메서드 레퍼런스로 축약
    Function<Car, String> f2 = Car::getCarName;
    String carName2 = f2.apply(new Car("k5"));
    
}

 

 Object::instance method 메서드 레퍼런스 예

 

public class CarInventory {
    private int incomingCount;
    private int totalCount;

    public CarInventory(int totalCount) {
        this.totalCount = totalCount;
    }

    public int getTotalCount(int incomingCount){
        return totalCount + incomingCount;
    }
}
public static void main(String[] args) {

    final CarInventory carInventory = new CarInventory(10);
	
    // 축약전
    Function<Integer, Integer> f1 = count -> carInventory.getTotalCount(count);
    int totalCount1 = f1.apply(10);

    // 람다 표현식을 메서드 레퍼런스로 축약
    Function<Integer, Integer> f2 = carInventory::getTotalCount;
    int totalCount2 = f2.apply(20);



    // T -> T : UnaryOperator 사용
    UnaryOperator<Integer> f3 = carInventory::getTotalCount;
    int totalCount3 = f3.apply(30);

    // Integer -> Integer : IntUnaryOperator 사용
    IntUnaryOperator f4 = carInventory::getTotalCount;
    int totalCount4 = f4.applyAsInt(40);
}

람다 표현식 밖에 있는 인스턴스를 참조(캡쳐링)

 

 

Constructor::new 메서드 레퍼런스 예

public static void main(String[] args) {

	// 축약전
	Function<String, Car> f1 = s -> new Car(s);
	Car car1 = f1.apply("콜로라도");

	// 람다식을 메서드 레퍼런스로 축약 후 
	Function<String, Car> f2 = Car::new;
	Car car2 = f2.apply("카니발");
}

 

 

 

 


더보기

 

 

 

연습

람다식 -> 레퍼런스 예제 더보기

// 문제 1번
Predicate<String> p1 = s -> s.isEmpty();

// 문제 1번 정답
Predicate<String> p2 = String::isEmpty;
System.out.println("문제 1번 결과: " + p2.test("Not Empty"));



// 문제 2번
Function<Integer, String> f1 = i -> String.valueOf(i);

// 문제 2번 정답
Function<Integer, String> f2 = String::valueOf;
System.out.println("문제 2번 결과: " + f2.apply(3));



// 문제 3번
BiPredicate<List<Integer>, Integer> bp1 = (list, num) -> list.contains(num);

// 문제 3번 정답
BiPredicate<List<Integer>, Integer> bp2 = List::contains;
System.out.println("문제 3번 결과: " + bp2.test(Arrays.asList(1, 3, 5, 7, 9), 9));



//문제 4번
Consumer<String> c1 = s -> System.out.println(s);

// 문제 4번 정답
Consumer<String> c2 = System.out::println;
c2.accept("문제 4번 결과: Hello!");



// 문제 5번
BiFunction<String, CarType, Car> bf1 = (s, carType) -> new Car(s, carType);

// 문제 5번 정답
BiFunction<String, CarType, Car> bf2 = Car::new;
Car car = bf2.apply("말리부", CarType.SEDAN);
System.out.println("문제 5번 결과: " + car.getCarName() + " / " + car.getCarType());

 

 

 

 

정리

 

부모 클래스 상속받아 자식 클래스 구현해서 사용

-> 익명 클래스로 구현해서 사용

-> 함수형 프로그래밍을 위해 람다 표현해서 사용

-> 메서드 레퍼런스 사용

 

 

참고

www.slideshare.net/heechanglee7/8-49928187

 

 

 

 

 

 

 

 

 

728x90
반응형

'Language > Java' 카테고리의 다른 글

그리디 - 당장 좋은 것만 선택하는 그리디  (0) 2021.05.05
DFS/BFS - 탐색 알고리즘  (0) 2021.05.05
Java 컬렉션(Collection)  (0) 2021.04.22
Java 자료형  (0) 2021.04.22
POJO, DAO, DTO, VO 차이  (0) 2021.04.22