ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 함수형 프로그래밍 실전 예제(함수형 인터페이스, 람다 함수)
    Java/Java 문법 종합반 강의 정리 2024. 1. 9. 12:08

    기존에 사용하던 문법으로 예제를 만들고, 그것을 함수형 문법으로 리팩토링 해보자

     

    // 주차장 예제
    // 티켓, 파킹머니 -> 주차하게 가능
    public class LambdaAndStream {
        public static void main(String[] args) {
            // 주차대상 차량
            ArrayList<Car> carsWantToPart = new ArrayList<>();
            // 주차장
            ArrayList<Car> parkingLot = new ArrayList<>();
    
            Car car1 = new Car("Benz", "Class E", true, 0);
            Car car2 = new Car("BMW", "Series 7", false, 100);
            Car car3 = new Car("BMW", "X9", false, 0);
            Car car4 = new Car("Audi", "A7", true, 0);
            Car car5 = new Car("Hyundai", "Ionic 6", false, 10000);
    
            carsWantToPart.add(car1);
            carsWantToPart.add(car2);
            carsWantToPart.add(car3);
            carsWantToPart.add(car4);
            carsWantToPart.add(car5);
    
            parkingLot.addAll(parkingCarWithTicket(carsWantToPart));
            parkingLot.addAll(parkingCarWithMoney(carsWantToPart));
    
            for(Car car : parkingLot){
                System.out.println("Parked Car : " + car.getCompany() + "-" + car.getModel());
            }
        }
    
        public static List<Car> parkingCarWithTicket(List<Car> carsWantToPart){
            ArrayList<Car> cars = new ArrayList<>();
    
            for(Car car : carsWantToPart){
                if(car.hasParkingTicket()){
                    cars.add(car);
                }
            }
            return cars;
        }
    
        public static List<Car> parkingCarWithMoney(List<Car> carsWantToPart){
            ArrayList<Car> cars = new ArrayList<>();
    
            for(Car car : carsWantToPart){
                if(!car.hasParkingTicket() && car.getParkingMoney() > 1000){
                    cars.add(car);
                }
            }
            return cars;
        }
    }
    
    class Car{
        private final String company;
        private final String model;
    
        private final boolean hasParkingTicket;
        private final int parkingMoney;
    
        public Car(String company, String model, boolean hasParkingTicket, int parkingMoney) {
            this.company = company;
            this.model = model;
            this.hasParkingTicket = hasParkingTicket;
            this.parkingMoney = parkingMoney;
        }
    
        public String getCompany(){
            return company;
        }
    
        public String getModel(){
            return model;
        }
    
        public boolean hasParkingTicket(){
            return hasParkingTicket;
        }
    
        public int getParkingMoney(){
            return parkingMoney;
        }
    }

     

    티켓 혹은 1000원 이상의 money 가 있는 객체만 주차장인 pockingLog 에 추가하여 출력하는 예제이다.

     

    함수를 값으로 전달하기 : 함수형 인터페이스

    • 함수를 값으로 전달하려면 어떻게 해야할까?
    • 일단 메소드에 파라미터를 전달하는 경우를 생각해보자.
    // 음.. ??? 에 무엇을 넣어야 할까요?
    public exampleMethod(int parameter1, ??? parameterFunction) { ... }
    • 일단 타입을 정의해줘야 한다.
    • 이럴때 사용하는 것이 함수형 인터페이스 이다.
    • 인터페이스는 타입 역할을 할 수 있다.(멀티 리모컨 예제처럼)
    • 함수를 전달할 때 타입을 알려주기 위해서 함수형 인터페이스를 선언하거나 사용해야 한다.
    • 함수형 인터페이스는 다음과 같은 특징을 가지고 있다.
      • 추상 메소드를 딱 하나만 가지고 있는 인터페이스
      • @FunctionalInterface 어노테이션으로 검증 할 수 있음

     

    • 그러면 함수형 인터페이스와 함수를 값으로 전달하기 위해 값으로 전달할 함수를 먼저 만들어보자.
        // Car 클래스 내부에 두 메서드 구현
        public static boolean hasTicket(Car car){
            return car.hasParkingTicket;
        }
        
        public static boolean noTicketButMoney(Car car){
            return !car.hasParkingTicket && car.getParkingMoney() > 1000;
        }
    • 함수형 인터페이스를 다음과 같이 정리한다.
        interface Predicate<T>{
            boolean test(T t);
        }

     

    • 이것들을 사용해 리팩토링할 함수는 아래의 두 함수이다.
    public static List<Car> parkingCarWithTicket(List<Car> carsWantToPark) {
            ArrayList<Car> cars = new ArrayList<>();
    
            for (Car car : carsWantToPark) {
                if (car.hasParkingTicket()) {
                    cars.add(car);
                }
            }
    
            return cars;
      }
    
    public static List<Car> parkingCarWithMoney(List<Car> carsWantToPark) {
        ArrayList<Car> cars = new ArrayList<>();
    
        for (Car car : carsWantToPark) {
            if (!car.hasParkingTicket() && car.getParkingMoney() > 1000) {
                cars.add(car);
            }
        }
    
        return cars;
    }

     

    굉장히 유사한 로직을 가진 두 함수를 하나로 합칠 것인데, 주목해야 할 점은 내부 주요 로직을 함수로 전달받는 것이다.

    함수를 값으로 처리할 수 있게 된다면, 거의 똑같은 두개의 함수는 필요가 없다.

    // 변경점 1 : Predicate<Car> 인터페이스를 타입 삼아 함수를 전달합니다!
    public static List<Car> parkCars(List<Car> carsWantToPark, Predicate<Car> function) {
          List<Car> cars = new ArrayList<>();
    
          for (Car car : carsWantToPark) {
    					// 변경점 2 : 전달된 함수는 다음과 같이 사용됩니다!
              if (function.test(car)) {
                  cars.add(car);
              }
          }
    
          return cars;
      }

     

    실제 사용 방법은 이렇다.

    parkingLot.addAll(parkCars(carsWantToPark, Car::hasTicket));
    parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));

     

    Car 클래스 내부에 있는 함수를 매개변수로 사용할 수 있게 된 모습이다.

     

    최종적으로 코드의 변화는 이렇다.

     

    import java.util.ArrayList;
    import java.util.List;
    
    public class LambdaAndStream {
        public static void main(String[] args) {
            ArrayList<Car> carsWantToPark = new ArrayList<>();
            ArrayList<Car> parkingLot = new ArrayList<>();
    
            Car car1 = new Car("Benz", "Class E", true, 0);
            Car car2 = new Car("BMW", "Series 7", false, 100);
            Car car3 = new Car("BMW", "X9", false, 0);
            Car car4 = new Car("Audi", "A7", true, 0);
            Car car5 = new Car("Hyundai", "Ionic 6", false, 10000);
    
            carsWantToPark.add(car1);
            carsWantToPark.add(car2);
            carsWantToPark.add(car3);
            carsWantToPark.add(car4);
            carsWantToPark.add(car5);
    
            parkingLot.addAll(parkCars(carsWantToPark, Car::hasTicket));
            parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));
    
    
            for (Car car : parkingLot) {
                System.out.println("Parked Car : " + car.getCompany() + "-" + car.getModel());
            }
    
    
        }
    
        public static List<Car> parkCars(List<Car> carsWantToPark, Predicate<Car> function) {
            List<Car> cars = new ArrayList<>();
    
            for (Car car : carsWantToPark) {
                if (function.test(car)) {
                    cars.add(car);
                }
            }
    
            return cars;
        }
    
    
    }
    
    class Car {
        private final String company; // 자동차 회사
        private final String model; // 자동차 모델
    
        private final boolean hasParkingTicket;
        private final int parkingMoney;
    
        public Car(String company, String model, boolean hasParkingTicket, int parkingMoney) {
            this.company = company;
            this.model = model;
            this.hasParkingTicket = hasParkingTicket;
            this.parkingMoney = parkingMoney;
        }
    
        public String getCompany() {
            return company;
        }
    
        public String getModel() {
            return model;
        }
    
        public int getParkingMoney() {
            return parkingMoney;
        }
    
        public static boolean hasTicket(Car car) {
            return car.hasParkingTicket;
        }
    
        public static boolean noTicketButMoney(Car car) {
            return !car.hasParkingTicket && car.getParkingMoney() > 1000;
        }
    }
    
    interface Predicate<T> {
        boolean test(T t);
    }

     

    이어서 이 예제를 사용해 람다: 익명함수로 함수를 간단하게 사용해보자.

    만약 주차장에 주말은 돈도 있고 티켓도 있어야 주차를 가능하게 만드는 새로운 정책을 세운다고 가정하자.

    하지만 주말에 한 번 사용하고 말건데, 메서드로 구현하기가 너무 귀찮다.

     

    이럴 땐 람다 익명함수를 이용하면 된다.

    // 주말의 주차장 추가
    ArrayList<Car> weekendParkingLot = new ArrayList<>();
    
    weekendParkingLot
    .addAll(parkCars(carsWantToPark, (Car car) -> car.hasParkingTicket() && car.getParkingMoney() > 1000));

     

    • 함수를 값으로 전달하는데, 어딘가에 구현하지 않고 그냥 간단하게 구현해서 넘기고 싶으면 람다식을 이용하자.
    • 람다식은 "함수 값"으로 평가되며, 한번만 사용된다.
    • 문법도 그만큼 간결하게 작성할 수 있다.

    람다 함수 문법

    // 기본적으로 문법은 다음과 같습니다.
    (파라미터 값, ...) -> { 함수 몸체 }
    
    // 아래의 함수 두개는 같은 함수입니다.
    // 이름 반환타입, return문 여부에 따라 {}까지도 생략이 가능합니다.
    public int toLambdaMethod(int x, int y) {
    	return x + y;
    }
    
    (x, y) -> x + y
    
    // 이런 함수도 가능하겠죠?
    public int toLambdaMethod2() {
    	return 100;
    }
    
    () -> 100
    
    // 모든 유형의 함수에 가능합니다.
    public void toLambdaMethod3() {
    	System.out.println("Hello World");
    }
    
    () -> System.out.println("Hello World")

    'Java > Java 문법 종합반 강의 정리' 카테고리의 다른 글

    null은 실수? Optional의 등장  (0) 2024.01.09
    stream(스트림) 개념, 예제  (6) 2024.01.09
    Java8 에서의 변경점  (1) 2024.01.09
    Lock, Conditon  (6) 2024.01.03
    wait(), notify()  (1) 2024.01.03
Designed by Tistory.