개발/개발노트

[Java] 제네릭 메서드 활용 예제

Gamii 2024. 6. 4. 07:50
728x90

[예제 코드]

 

 

 

 

1. Bean 

 

 

 

 

package generics_v1;

import java.time.LocalDate;

public class FruitBean {
    private int price;
    private LocalDate entryDate;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public LocalDate getEntryDate() {
        return entryDate;
    }

    public void setEntryDate(LocalDate entryDate) {
        this.entryDate = entryDate;
    }
}

 

 

package generics_v1;

public class AppleBean extends FruitBean{
    private String color;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

 

 

package generics_v1;

public class BananaBean extends FruitBean{
    private String country;

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }
}

 

 

 

CoffeeBean은 FruitBean을 상속받지 않았다.

package generics_v1;

public class CoffeeBean {
    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

 

 

2. 전체 가격 합계를 구하는 메서드

FruitBean을 상속 받는 Bean만 사용할 수 있도록 <T extends FruitBean> 상한경계를 사용했다. 

 

package generics_v1;

import java.util.List;

public class Calculator {

    //제네릭 타입 매개변수의 바운드는 메서드 선언 부분에서 지정
    public <T extends FruitBean> int totalPriceSum(List<T> list){
        int totalSum = 0;

        totalSum = list.stream().mapToInt(FruitBean::getPrice).sum();

        return totalSum;
    }
}

 

 

 

3. 테스트 코드

package study;

import generics_v1.AppleBean;
import generics_v1.BananaBean;
import generics_v1.Calculator;
import generics_v1.CoffeeBean;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class GenericsTest {
    public Calculator cal;

    @BeforeEach
    public void setUp(){
        cal = new Calculator();
    }

    @DisplayName("AppleBean Test")
    @Test
    public void appleTotalSum(){
        List<AppleBean> list = new ArrayList<>();

        for(int i = 100; i < 400; i=i+100){
            AppleBean bean = new AppleBean();
            bean.setPrice(i);
            list.add(bean);
        }

        assertEquals(600, cal.totalPriceSum(list));
    }

    @DisplayName("BananaBean Test")
    @Test
    public void bananaTotalSum(){
        List<BananaBean> list = new ArrayList<>();

        for(int i = 100; i < 500; i=i+100){
            BananaBean bean = new BananaBean();
            bean.setPrice(i);
            list.add(bean);
        }

        assertEquals(1000, cal.totalPriceSum(list));
    }

    @DisplayName("No FruitBean Test")
    @Test
    public void CoffeeTotalSum(){
        List<CoffeeBean> list = new ArrayList<>();

        for(int i = 100; i < 500; i=i+100){
            CoffeeBean bean = new CoffeeBean();
            bean.setPrice(i);
            list.add(bean);
        }

        //컴파일 시점에 Error
        //reason: no instance(s) of type variable(s) exist so that CoffeeBean conforms to FruitBean
        assertEquals(1000, cal.totalPriceSum(list));
    }
}

 

 

의도했던 대로 FruitBean을 상속받는 Bean만 사용이 가능해서 CoffeeTotalSum 메서드에서 Calculator의 totalPriceSum을 사용하지 못해 컴파일 에러가 발생했다.

 

 

 

 

 

 

[느낀점]

 

제네릭 메서드 예제를 만들며 느낀 점을 공유하려고 한다.

 

 

 

 

1. 컴파일시에 에러를 발생시키는 코드를 만들자.

- 자바를 공부할 때마다 컴파일시에 에러를 발생시키는게 장점으로 나온 개념들이 많았다. 이론을 공부할 때는 필요성에 대해 느끼지 못했지만, 예제 코드나 실무에서 코드를 만들 때 컴파일 시에 에러가 발생하면 이유를 알고 바로 수정이 가능해서 좋다.

 

 

 

 

 

2. 제네릭 메서드의 상한경계, 하한경계를 표시할 땐 메서드 선언 부분에 표시한다.

 

1) 제네릭 타입 매개변수에 제한을 설정하려면 메서드 선언 부분에 <T extends FruitBean>를 사용한다.

2) 메서드 매개변수로 List<T>를 사용한다.

 

 

틀린 예제

public <T> int totalPriceSumError(List<T extends FruitBean> list){
    int totalSum = 0;

    totalSum = list.stream().mapToInt(FruitBean::getPrice).sum();

    return totalSum;
}

 

 

올바른 예제

public <T extends FruitBean> int totalPriceSum(List<T> list){
    int totalSum = 0;

    totalSum = list.stream().mapToInt(FruitBean::getPrice).sum();

    return totalSum;
}

 

 

FruitBean을 상속 받으면서 price 필드가 있는 것을 보장해야한다. 그래야 메서드에서 에러가 발생하지 않는다.

 

 

 

 

3. 간단한 로직 검증할 때 테스트 코드를 사용하자.

- 아직 테스트코드를 잘 활용하지는 못하지만, 이번 예제를 만들면서 내가 예상했던 값이 나오는지 로직 확인할 때 사용했다. 간단하게 빠르게 확인 할 수 있어서 좋았다. 다양하게 테스트 코드를 작성하는 방법을 알아보자.

 

 

 

 

 

 

 

[참고]

 

[Java] 제네릭의 이해2

제네릭의 이해 2에서는 제네릭 타입 제한과 와일드카드에 대해서 다뤄보려고 한다.     [제네릭의 이해 1 ] 링크는 아래 참고..  [Java] 제네릭의 이해1이번에 기능 개발에 참고하고 싶은 코드

run-ran-run-ant.tistory.com