-
함수형 프로그래밍 실전 예제(함수형 인터페이스, 람다 함수)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