본문 바로가기

개발공부/Java

패스트캠퍼스 환급챌린지 26일차 : 한 번에 끝내는 컴퓨터 공학 & 인공지능 복수전공 초격차 패키지 강의 후기

반응형

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

CH 11. 객체지향 고급 프로그래밍 - 향상된 자바 API 활용

 

오늘은 좀 더 세련되고 유연한 코드를 작성할 수 있게 돕는 고급 기법들을 알아보려고 합니다. 바로 람다 표현식(Lambda Expression)Enum(열거 타입)의 활용법입니다. 이 두 가지는 자바의 강력함을 한층 더 끌어올려, 코드를 간결하게 만들고 가독성을 높이며, 궁극적으로는 개발자의 생산성 향상에 기여합니다.


1. 람다 표현식: 익명 클래스의 간결한 진화 

자바 8부터 도입된 람다 표현식은 함수형 프로그래밍 스타일을 자바에 적용할 수 있게 해준 혁신적인 기능입니다. 이는 기본적으로 익명 함수(Anonymous Function)를 표현하는 간결한 문법이며, 보통 하나의 추상 메소드만을 가지는 인터페이스(함수형 인터페이스)의 인스턴스를 즉석에서 생성할 때 사용됩니다.

1.1. 익명(Anonymous) 클래스로부터 람다까지

람다 표현식이 등장하기 전에는, 특정 인터페이스를 즉석에서 구현해야 할 때 익명 클래스(Anonymous Class)를 사용했습니다. 익명 클래스는 정의될 때 이름이나 식별자에 바인딩되지 않는 클래스입니다. 예를 들어, 쇼핑몰 시스템에서 마일리지 부여 기준을 정의하는 MileageCriteria 인터페이스가 있다고 가정해 봅시다.

// MileageCriteria.java (함수형 인터페이스)
/**
 * 마일리지 부여 기준을 명확하게 드러내기 위한 기준을 담습니다.
 */
public interface MileageCriteria { //
    /**
     * 마일리지 부여 대상 주문인지 확인합니다.
     *
     * @param order 마일리지 대상 주문 (정상 판매분인지 판단)
     * @param customer 전화번호를 공개하는 고객 대상 확인
     * @return 마일리지 부여 기준에 적합하면 참(true) 아니면 거짓 (false)을 반환합니다.
     */
    public boolean isAvailable(Order order, Customer customer); // 추상 메소드 하나만 가짐
}

이 MileageCriteria를 익명 클래스로 구현하여 마일리지 부여 로직을 유연하게 변경할 수 있습니다. 예를 들어, "할인되지 않은 주문이면서 고객이 회원인 경우에만 마일리지 적립"이라는 기준을 적용해볼 수 있습니다.

// ShoppingContext.java (applyTodaysMileage 메소드와 함께)
import java.util.Collection; //

// ... Order, Customer 클래스는 이전 포스팅의 것을 사용 ...

public class ShoppingContext {
    // ... 기존 필드 및 메소드 ...
    private Map<Integer, Order> orderMap; // 주문 맵 (초기화 필요)

    // applyTodaysMileage 메소드: 마일리지 기준(criteria)을 받아 처리
    private void applyTodaysMileage(Collection<Order> orders, MileageCriteria criteria) { //
        for (Order order : orders) { //
            // 마일리지 부여 기준(criteria)에 따라 마일리지 적립 여부 판단
            if (criteria.isAvailable(order, order.getCustomer())) { //
                Customer customer = order.getCustomer();
                customer.addMileage(order.getSalePrice() / 10); // 10% 마일리지 적립 (가정: Customer에 addMileage 메소드 추가)
                System.out.println("마일리지 부여: " + customer.toString() + " | 적립액: " + (order.getSalePrice() / 10) + "원");
            }
        }
    }

    // close 메소드: 판매 종료 후 마일리지 정산
    public void close() { //
        System.out.println("\n--- 판매 종료 및 마일리지 정산 시작 ---");

        // 익명 클래스를 사용한 마일리지 부여 (기존 방식)
        applyTodaysMileage(this.orderMap.values(), new MileageCriteria() { //
            @Override
            public boolean isAvailable(Order o, Customer c) { //
                // "할인되지 않은 주문" && "고객이 전화번호를 공개하는 회원" (예시 기준)
                return !o.isDiscounted() && (c.getNumber() != null && !c.getNumber().isEmpty()); //
            }
        });
        System.out.println("--- 마일리지 정산 완료 ---");
    }

    // ... init(), order() 등 다른 메소드 ...

    // Order 클래스에 isDiscounted(), getSalePrice() 메소드 추가 (가정)
    // Customer 클래스에 addMileage() 메소드, getNumber() 메소드 추가 (가정)
    // (이전 코드에서 Order와 Customer가 완전하지 않을 수 있으므로 추가 가정)
    // Order 클래스 (샘플)
    public class Order {
        private Customer customer;
        private Product product;
        private int finalSalePrice; // 최종 판매 가격
        private boolean isDiscountedOrder; // 이 주문이 할인 적용되었는지 여부

        public Order(Customer customer, Product product) {
            this.customer = customer;
            this.product = product;
            this.finalSalePrice = product.getPrice(); // 기본적으로 상품의 가격
            this.isDiscountedOrder = false;
        }

        // 할인 적용된 주문 생성자 (옵션)
        public Order(Customer customer, Product product, int discountedPrice) {
            this(customer, product);
            this.finalSalePrice = discountedPrice;
            this.isDiscountedOrder = true;
        }

        public int getSalePrice() { return finalSalePrice; }
        public boolean isDiscounted() { return isDiscountedOrder; }
        public Customer getCustomer() { return customer; }
        // ... toString() 등 다른 메소드 ...
    }

    // Customer 클래스 (샘플)
    // (이전 포스팅에서 이미 정의되었지만, addMileage 메소드 추가 가정)
    // public class Customer {
    //     private String name;
    //     private String number;
    //     private String email;
    //     private int mileage = 0; // 마일리지 필드 추가
    //
    //     public Customer(String name, String number, String email) { ... }
    //
    //     public String getName() { return name; }
    //     public String getNumber() { return number; } // 전화번호 반환 메소드
    //     public String getEmail() { return email; }
    //
    //     public void addMileage(int amount) { this.mileage += amount; }
    //     public int getMileage() { return mileage; }
    //
    //     @Override
    //     public String toString() { ... (마일리지 포함하도록 수정 가능) }
    // }
}

익명 클래스는 유연성을 제공하지만, 코드가 다소 장황해질 수 있습니다. 여기서 람다 표현식이 빛을 발합니다. 람다 표현식은 식별자에 바인딩되지 않는 함수 정의(function definition)를 의미하며, 자바 8부터 지원됩니다. 익명 클래스를 굳이 정의할 필요 없이, 간결한 문법으로 동일한 로직을 표현할 수 있게 됩니다.

// ShoppingContext.java (람다 표현식 적용)
// ... applyTodaysMileage 메소드, Order, Customer 클래스 정의는 동일 ...

public class ShoppingContext {
    // ... 기존 필드 및 메소드 ...

    public void close() {
        System.out.println("\n--- 판매 종료 및 마일리지 정산 시작 (람다) ---");

        // 람다 표현식을 사용한 마일리지 부여 (훨씬 간결!)
        // (o, c) -> !o.isDiscounted() && (c.getNumber() != null && !c.getNumber().isEmpty())
        // (o, c): 매개변수 목록 (Order o, Customer c)
        // ->: 화살표 토큰
        // !o.isDiscounted() && (c.getNumber() != null && !c.getNumber().isEmpty()): 메소드 본문
        applyTodaysMileage(this.orderMap.values(), (o, c) -> !o.isDiscounted() && (c.getNumber() != null && !c.getNumber().isEmpty())); //
        System.out.println("--- 마일리지 정산 완료 (람다) ---");
    }
}

매개변수가 하나이면 괄호(())를 생략할 수 있고, 메소드 본문이 한 줄이면 중괄호({})와 return 키워드를 생략할 수 있습니다. 람다 표현식은 코드의 양을 줄여 가독성을 높이고, 함수형 인터페이스의 활용을 극대화합니다. 오라클 자바 튜토리얼에서 더 자세한 내용을 찾아볼 수 있습니다.


2. Enum: 값의 범주를 한정하는 강력한 타입 

프로그래밍을 하다 보면 특정 값의 종류가 몇 가지로 한정되는 경우가 많습니다. 예를 들어, 요일, 계절, 혹은 쇼핑몰의 '판매 유형' 같은 것들 말이죠. 이러한 경우에 문자열이나 정수를 사용하는 대신, 열거 타입(Enumerated type), 줄여서 Enum을 활용하면 코드의 안정성과 가독성을 크게 높일 수 있습니다.

컴퓨터 프로그래밍에서 열거 타입은 이름이 있는 값들의 집합으로 구성된 데이터 타입입니다. 자바의 Enum은 단순한 상수가 아니라, 실제로 컴파일러가 생성하는 특별한 종류의 클래스입니다. Enum의 값들은 해당 클래스의 전역적으로 미리 생성된 인스턴스처럼 동작합니다.

2.1. Enum 적용 예시: 판매 유형 관리

쇼핑몰 정산 과정에서 '정상 판매'와 '할인 판매'를 구분하여 요약해야 하는 상황을 생각해 봅시다. 이때 SaleType이라는 Enum을 정의하면 코드가 훨씬 명확해집니다.

// SaleType.java (Enum 정의)
public enum SaleType { //
    NORMAL,      // 정상 판매
    DISCOUNTED   // 할인 판매
}

// Order.java (SaleType 필드 추가 및 isDiscounted() 대체)
// 기존 isDiscounted() 메소드를 SaleType으로 대체할 수 있습니다.
public class Order {
    // ... 기존 필드 ...
    private SaleType saleType; // 판매 유형 필드 추가

    public Order(Customer customer, Product product) {
        this.customer = customer;
        this.product = product;
        this.finalSalePrice = product.getPrice();
        this.saleType = SaleType.NORMAL; // 기본은 정상 판매
    }

    // 할인 적용된 주문 생성자
    public Order(Customer customer, Product product, int discountedPrice) {
        this(customer, product);
        this.finalSalePrice = discountedPrice;
        this.saleType = SaleType.DISCOUNTED; // 할인 판매로 설정
    }

    public int getSalePrice() { return finalSalePrice; }
    // public boolean isDiscounted() { return saleType == SaleType.DISCOUNTED; } // isDiscounted()는 이제 SaleType으로 대체 가능
    public SaleType getSaleType() { return saleType; } // SaleType 반환 메소드
    // ...
}

// ShoppingContext.java (Enum을 활용한 판매 요약)
public class ShoppingContext {
    // ... 기존 필드 및 메소드 ...

    // 판매 요약 메소드
    public void summarizeSales() { //
        int countOfNormalSales = 0;
        long sumOfNormalSales = 0;
        int countDiscountedSales = 0;
        long sumOfDiscountedSales = 0;

        System.out.println("\n--- 판매 요약 시작 ---");
        for (Order o : this.orderMap.values()) { // 모든 주문 순회
            switch (o.getSaleType()) { // Enum 값을 기준으로 분기 처리
                case NORMAL: // 정상 판매
                    countOfNormalSales++;
                    sumOfNormalSales += o.getSalePrice();
                    break;
                case DISCOUNTED: // 할인 판매
                    countDiscountedSales++;
                    sumOfDiscountedSales += o.getSalePrice();
                    break;
                default:
                    // 예상치 못한 SaleType 처리
                    break;
            }
        }
        System.out.println("정상 판매 건수: " + countOfNormalSales + ", 총액: " + sumOfNormalSales + "원");
        System.out.println("할인 판매 건수: " + countDiscountedSales + ", 총액: " + sumOfDiscountedSales + "원");
        System.out.println("--- 판매 요약 완료 ---");
    }

    // close 메소드 (람다 사용)
    public void close() {
        // ... 마일리지 정산 로직 (람다 사용) ...
        applyTodaysMileage(this.orderMap.values(), (o, c) -> o.getSaleType() == SaleType.NORMAL && (c.getNumber() != null && !c.getNumber().isEmpty())); //
        // 람다 식의 isDiscounted() 대신 getSaleType() 활용
        summarizeSales(); // 판매 요약 호출
    }
}

// FrameworkExample.java (main 메소드에서 summarizeSales() 호출 추가)
public class FrameworkExample {
    public static void main(String[] args) {
        ShoppingContext shoppingContext = new ShoppingContext();
        Map<String, Product> catalog = shoppingContext.prepareCatalog();
        List<Customer> customers = shoppingContext.getSimulatedCustomerList(4);
        shoppingContext.init();

        // 주문 시뮬레이션
        shoppingContext.order(customers.get(0), catalog.get("book1")); // 정상 판매
        shoppingContext.order(customers.get(1), catalog.get("food1")); // 정상 판매 (초기)
        shoppingContext.startDiscount("food1"); // 떡볶이 할인 시작
        shoppingContext.order(customers.get(2), catalog.get("food1")); // 할인 판매
        shoppingContext.order(customers.get(3), catalog.get("food3")); // 정상 판매

        shoppingContext.close(); // 판매 종료 및 정산
    }
}

Enum을 사용하면 switch 문을 통한 분기 처리가 훨씬 간결하고 명확해집니다. 또한, 미리 정의된 값들만 사용하도록 강제하여 잘못된 값을 사용하는 것을 컴파일 시점에 방지할 수 있습니다. 이는 코드의 안정성을 높이는 데 크게 기여합니다.


3. 마무리

  • 람다 표현식은 익명 클래스의 장점을 유지하면서도 코드의 장황함을 줄여, 함수형 프로그래밍 스타일을 자바에 녹여낼 수 있게 해줍니다. 이는 특히 이벤트 핸들링이나 콜백 함수를 정의할 때 코드의 가독성을 비약적으로 향상시킵니다.
  • Enum은 제한된 값의 집합을 안전하고 의미 있게 표현할 수 있도록 도와줍니다. 이를 통해 문자열 상수의 오타나 잘못된 값 할당으로 인한 런타임 오류를 방지하고, 코드의 유지보수성을 크게 개선할 수 있습니다.

오늘의 실습

 

 

오늘도 실무에서 많이 사용하는 코드를 복습했다. 람다식은 익숙하지 않아서 실제로 잘 사용하지 않았는데, 코드가 간결해지는게 좋아보였다.(물론 코드 간결화가 가장 중요한 요소는 아니지만...ㅎㅎ 코드를 줄여야 할 땐 넘나 유용할 것 같았다.) 

 

https://fastcampus.info/4n8ztzq

 

(~6/20) 50일의 기적 AI 환급반💫 | 패스트캠퍼스

초간단 미션! 하루 20분 공부하고 수강료 전액 환급에 AI 스킬 장착까지!

fastcampus.co.kr

 

반응형