본문 바로가기

JAVA

[Java] 제네릭(Generic), 왜 사용할까?

 

 

 

 

개요

정확히 같은 작업을 수행하는 클래스가 타입만 다르다면, 다음 코드에서와 같이 불필요한 코드 중복이 생기게된다.

더보기

Main

public static void main(String[] args) {
    IntegerBox integerBox = new IntegerBox();
    integerBox.setValue(10);
    System.out.println("integerBox = " + integerBox.getValue());

    DoubleBox doubleBox = new DoubleBox();
    doubleBox.setValue(10.2);
    System.out.println("doubleBox = " + doubleBox.getValue());
}

 

DoubleBox

public class DoubleBox {

    private Double value;

    public void setValue(Double value) {
        this.value = value;
    }

    public Double getValue() {
        return value;
    }
}

 

IntegerBox

public class IntegerBox {

    private Integer value;

    public void setValue(Integer value) {
        this.value = value;
    }

    public Integer getValue() {
        return value;
    }
}

이러한 코드 중복을 막기 위해 제네릭을 사용한다. 그렇다면, 레네릭이 무엇일까?

 

 


 

 

목차

1. Object 타입

2. 제네릭 클래스

  • 제네릭 특징
  • 제네릭 클래스 기본구조
  • 제네릭 클래스 사용 예제
  • 제네릭 클래스 활용 예제
  • 제네릭 클래스 타입 제한

3. 제네릭 메서드

  • 제네릭 메서드 특징
  • 제네릭 메서드 기본 구조
  • 제네릭 메서드 사용 예제

 

 


 

 

Object 타입

모든 타입의 부모 클래스인 Object를 사용하면 되지 않을까? 

더보기

Main

public class ObjectMain {
    public static void main(String[] args) {
        ObjectBox doublebox = new ObjectBox();
        doublebox.setValue("10.2");
        System.out.println("doubleBox = " + doublebox.getValue());
    }
}

 

ObjectBox

public class ObjectBox {
    private Object value;

    public void setValue(Object value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }
}

Object를 사용하면, 다음과 같은 문제점이 있다.

  1. 다음과 같이 Object 타입으로 클래스를 만든 경우, 의도치 않게 다른 타입으로 넣을수 도 있다. (타입 안정성 ❌)
    • 이전 개요에서 보여준 코드의 경우는 타입 안정성이 좋다.
  2. 항상 반환 타입이 Object이므로, 내가 원하는 타입으로 다운 캐스팅해야 한다.

다형성을 이용하여, 여러 타입을 받을 수 있지만, 타입 안정성이 떨어지는 문제가 발생하였다.

 

 


 

 

제네릭 클래스

제네릭 클래스는 객체가 생성될 때 타입을 결정하는 클래스이다.. 

 

 

1. 제네릭 클래스 특징

  • 코드 중복을 줄일 수 있다.
  • 타입 안정성을 높일 수 있다. (컴파일 시점에 타입을 체크한다.)
  • 코드 가독성과 유지보수가 좋다.

 

2. 제네릭 클래스 기본 구조

class Box<T> {
    private T item;

    public void set(T item) {
        this.item = item;
    }

    public T get() {
        return item;
    }
}
  • `T` : 타입 매개변수 (Type Parameter)
  • 객체 선언 및 생성할 때, 실제 타입을 넣어주어야 한다. → ex.`Box <Interger> box = new Box<Integer>();`

 

3. 제네릭 클래스 사용 예제

더보기

Main

public class GenericMain {
    public static void main(String[] args) {
        GenericBox<Integer> integerBox = new GenericBox<>();
        //.setValue("10");
        System.out.println("integerBox = " + integerBox.getValue());
        
        GenericBox<String> stringBox = new GenericBox<>();
        stringBox.setValue("안녕하세요.");
        System.out.println("stringBox = " + stringBox.getValue());
    }
}

 

GenericBox

public class GenericBox <T> {

    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}
  • 컴파일러가 객체 생성 시점에, 타입 매개변수(<T>) 정보를 기반으로 타입을 결정한다.
  • 타입 안정성 ✅
  • 여러가지 값을 받을 수 있다. (재사용성 ✅)

 

4. 제네릭 클래스 활용 예제

다형성을 활용한 예제이다.

더보기

Main

public class CarMain {
    public static void main(String[] args) {
        CarInfo<Car> carInfo = new CarInfo<>();

        carInfo.setCar(new Kia("기아"));
        System.out.println("carInfo = " + carInfo.getCar());

        carInfo.setCar(new Tesla("테슬라"));
        System.out.println("carInfo = " + carInfo.getCar());
    }
}

 

CarInfo

public class CarInfo <T> {

    T car;

    public void setCar(T car) {
        this.car = car;
    }

    public T getCar() {
        return car;
    }
}

 

Car

public abstract class Car {
    private String name;

    public Car(String name) {
        this.name = name;
    }

    public void move() {
        System.out.println("차가 움직입니다.");
    }

    @Override
    public String toString() {
        return "Car{" +
                "car=" + name +
                '}';
    }
}

 

Kia

public class Kia extends Car{

    public Kia(String name) {
        super(name);
    }
}

 

Tesla

public class Tesla extends Car{

    public Tesla(String name) {
        super(name);
    }
}
  • 제네릭 인자로 부모 클래스를 넘겨줌으로써, CarInfo 클래스에 받을 수 있는 타입은 Car 클래스를 상속받고 있는 Kia와 Tesla이다.
  • 하지만 제네릭 인자로 여러가지 객체를 받을 수 있다는 단점이 존재한다. 즉, 제네릭 인자의 타입을 제한해야 한다.
더보기
CarInfo<Integer> carInfo = new CarInfo<>();

바로 위 코드에서와 같이 제네릭 인자로 Car 객체 타입이 아닌 다른 객체 타입으로 넣을 수 있다는 것이다. 즉, 어떤 타입이든 매개 인자로 넘길 수 있다.

 

 

5. 제네릭 클래스 타입 제한

제네릭 인자의 타입을 다음과 같이 `<T extends 객체>`로 제한할 수 있다.

public class CarInfo <T extends Car> {

    T car;

    public void setCar(T car) {
        this.car = car;
    }

    public T getCar() {
        return car;
    }
}
  • 전체(Object 객체)의 범위를 Car 객체로 제한하였다. 따라서 이전 코드에서처럼 Interger라는 객체는 받을 수 없고 Car를 상속받은 Kia 객체와 Tesla 객체만 제네릭 인자로 넘길 수 있게 되었다.

 

 


 

 

 

제네릭 메서드

제네릭 메서드는 메서드가 호출될 때 타입을 결정하는 메서드이다. 

 

 

1. 제네릭 메서드 특징

  • 클래스 전체가 제네릭이 아니더라도, 메서드 하나만 제네릭으로 사용할 수 있다.
  • 제네릭 클래스와 제네릭 메서드는 별개이다.
  • 제네릭 메서드도 제네릭 클래스와 마찬가지로 타입을 제한 할 수 있다. → `<S extends Number>`

 

2. 제네릭 메서드의 기본 구조

public static <S> S genericMethod(S item) {
    System.out.println("item = " + item);
    return item;
}
  • `S` : 타입 매개변수
  • <>는 반환 타입 앞에 넣어준다.
  • 제네릭 메서드를 호출할 때, 실제 타입을 넣어주어야 한다. → ex.`클래스명.<Integer>genericMethod(10);`
    • 제네릭 메서드의 매개변수와 반환 타입을 통해 제네릭 메서드의 타입을 유추할 수 있게 되어 <Integer>를 생략해도 오류가 생기지 않는다. → ex.`클래스명.genericMethod(10);` 

 

3. 제네릭 메서드 사용 예제

더보기

Main

public class GenericMethodMain {
    public static void main(String[] args) {
        String[] strArray = {"apple", "banana"};
        Integer[] intArray = {1, 2, 3};

        String firstStr = Util.getFirst(strArray); // T는 String
        Integer firstInt = Util.getFirst(intArray); // T는 Integer
    }
}

 

Util

class Util {
    public static <T> T getFirst(T[] array) {
        return array[0];
    }
}

 

  • 서로 다른 객체 배열이지만, 같은 메서드를 사용할 수 있다.
  • 꼭 제네릭 메서드는 static으로 만들어질 필요는 없다. 다만, 그렇게 되면 Util 객체를 생성한 후에 메서드를 호출해야 한다.

'JAVA' 카테고리의 다른 글

[Java] Lv_1 도서관 프로젝트  (0) 2025.05.01
[Java] 도전 과제 Lv_2 키오스크  (0) 2025.04.28
[Java] 도전 과제 Lv_1 키오스크  (0) 2025.04.26
[Java] Lv_4 & 5 키오스크 문제  (0) 2025.04.25
[Java] Lv_3 키오스크  (0) 2025.04.24