Java - Generic

JeongHoHyun·2024년 12월 30일

Java

목록 보기
14/23

✏️ Generic

  • <>를 사용한 클래스를 제네릭 클래스라 한다.
  • 제네릭 클래스를 사용할 때는 Integer, String 같은 타입을 미리 결정하지 않는다.
  • 대신에 클래스명 오른쪽에 <T> 와 같이 선언하면 제네릭 클래스가 된다.
    • 여기서 T를 타입 매개변수라고 한다.
    • 이 타입 매개변수는 이후에 Integer, String 으로 변환 할 수 있다.
  • 클래스 내부에 T 타입이 필요한 곳에 T value와 같이 타입 매개변수를 적어두면 된다.
  • 사용할 타입을 미리 결정하지 않고, 실제 사용하는 생성 시점에 타입을 결정한다.

📖 제네릭 적용 예제

public class GenericBox<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }
    
    public T get() {
        return value;
    }
}
public class BoxMain3 {
    
    public static void main(String[] args) {
        GenericBox<Integer> integerBox = new GenericBox<>();    // 생성 시점에 T의 타입이 결정
        integerBox.set(10); // Integer 타입만 허용, 그외 컴파일 오류
        Integer integer = integerBox.get(); // Integer 타입 반환 (캐스팅 X)
        System.out.println("integer = " + integer);

        GenericBox<String> stringBox = new GenericBox<>();
        stringBox.set("Hello");
        String string = stringBox.get();
        System.out.println("string = " + string);
        
        // 그 외 원하는 모든 타입 사용 가능
    }
}

row 타입

  • 제네릭 타입을 사용할 때는 항상 <>를 사용해서 사용시점에 원하는 타입을 지정해야 한다.
GenericBox integerBox = new GenericBox();
  • 위와 같이 <>을 지정하지 않을 수 이쓴데, 이런것을 로 타입(row type), 또는 원시타입 이라한다.
  • 원시 타입을 사용하면 내부의 타입 매개변수가 Object로 사용된다고 이해하면 된다.
  • 과거 코드와 호환이 필요해서 있는 기능이므로 사용하지 않는 것을 권장한다.
  • Object 타입을 사용해야 한다면 인자로 Object를 지정해서 사용하면 된다.
GenericBox<Object> integerBox = new GenericBox<>();

📌 타입 매개변수 제한

package generic;

import generic.animal.Animal;

public class AnimalHospitalV3 <T extends Animal>{
    private T animal;
    public void set(T animal){
        this.animal = animal;
    }

    public void checkup(){
        System.out.println("동물 이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());
    }

    public T getBigger(T target){
        return animal.getSize() > target.getSize() ? animal : target;
    }
}
  • 제한하지 않으면 모든 타입이 들어올 수 있다.
  • 핵심은 <T extends Animal>이다.
  • 타입 매개변수 T를 Animal과 그 자식만 받을 수 있도록 제한을 두는 것이다.
  • 자바 컴파일러는 T에 입력될 수 있는 값의 범위를 예측할 수 있다.
  • 따라서 Animal이 제거공하는 메서드 (getName(), getSize()) 같은 기능을 사용할 수 있다.

제네릭 메서드 (Generic Method)

package generic.ex4;

public class GenericMethod {
    public static Object objMethod(Object obj){
        System.out.println("Object print : " + obj);
        return obj;
    }

    public static <T> T genericMethod(T t){
        System.out.println("Generic print : " + t);
        return t;
    }

    public static <T extends Number> T numberMethod(T t){
        System.out.println("Object print : " + t);
        return t;
    }
}
package generic.ex4;

public class MethodMain1 {
    public static void main(String[] args) {
        Integer i = 10;
        Object object = GenericMethod.objMethod(i);

        // 타입인자 (Type Argument) 명시적 전달
        System.out.println("명시적 타입 인자 전달");
        Integer result = GenericMethod.<Integer>genericMethod(i);
        Integer integerValue = GenericMethod.<Integer>numberMethod(10);
        GenericMethod.<Double>numberMethod(20.0);

        // 타입 추론, 생략 가능
        Integer result1 = GenericMethod.genericMethod(15);
        Integer integerValue1 = GenericMethod.numberMethod(20);
    }
}
  • 정의 : <T> T genericMethod(T t)
  • 타입 인자 전달 : 메서드를 호출하는 시점
    • ex) GenericMethod.<Integer>genericMethod(i);
  • 제네릭 메서드는 클래스 전체가 아니라 특정 메서드 단위로 제네릭을 도입할 때 사용한다.
  • 제네릭 메서드를 정의할 때는 메서드의 반환 타입 왼쪽에 다이아몬드를 사용해서 <T>와 같이 타입 매개변수를 적어준다.
  • 타입 추론덕분에 컴파일러가 대신 처리하기 때문에 생략도 가능하다.
  • 제네릭 메서드는 메서드를 실제 호출하는 시점에 다이아몬드를 사용해서 <Integer>와 같이 타입을 정하고 호출한다.

🃏 와일드 카드 (wildcard)

  • 제네릭 타입을 조금 더 편리하게 사용할 수 있다.
  • 와일드 카드는 컴퓨터 프로그래밍에서 *, ? 와 같이 하나 이상의 문자들을 상징하는 특수 문자를 뜻한다.
package generic.ex5;

import generic.animal.Animal;

public class WildcardEx {
    static <T> void printGenericV1(Box<T> box){
        System.out.println("T = " + box.get());
    }

    static void printWildcardV1(Box<?> box){
        System.out.println("? = " + box.get());
    }

    static <T extends Animal> void printGeneticV2(Box<T> box){
        T t = box.get();
        System.out.println("이름 = " + t.getName());
    }

    static void printWildCardV2(Box<? extends Animal> box){
        Animal animal = box.get();
        System.out.println("이름 = " + animal.getName());
    }

    static <T extends Animal> T printAndReturnGenetic(Box<T> box){
        T t = box.get();
        System.out.println("이름 = " + t.getName());
        return t;
    }

    static Animal printAndReturnWildcard(Box<? extends Animal> box){
        Animal animal = box.get();
        System.out.println("이름 = " + animal.getName());
        return animal;
    }
}
  • 제네릭 메서드와 와일드 카드를 비교할 수 있게 같은 기능을 각각 하나씩 배치해두었다.

  • 와일드 카드는 ?를 사용해서 정의한다.

  • 와일드 카드에서도 마찬가지로 상한 제한을 둘 수 있다.

  • '? extends Animal'을 지정했다. 이렇게 하면 Animal과 그 하위 타입만 입력 받는다. 만약 다른 타입을 입력하면 컴파일 오류가 발생한다.

  • 결과적으로 Animal 타입의 기능을 호출할 수 있다.

    🛑 참고

    • 와일드 카드는 제네릭 타입이나, 제네릭 메서드를 선언하는 것이 이니다.
    • 이미 만들어진 제네릭 타입을 활용할 때 사용한다.
    • 와일드 카드는 메서드의 타입들을 타입 인자를 통해 변경할 수 없다. (일반 메서드에 사용한다)
    • 메서드 타입들을 특정 시점에 변경하려면 제네릭 타입이나, 제네릭 메서드를 사용해야한다.

    정리

    • 제네릭 타입이나 제네릭 메서드가 꼭 필요한 상황이면 <T>를 사용하고, 그렇지 않은 상황이면 와일드 카드를 사용하는 것을 권장한다.

    하한 와일드 카드

    // Animal 포함 상위 타입 전달 가능
    static void writeBox(Box<? super Animal> box) {
         box.set(new Dog("멍멍이", 100);
    }
    • 이 코드는 ? 가 Animal 타입을 포함한 Animal 타입의 상위 타입만 입력 받을 수 있다는 뜻이다.
    • 하한을 Animal로 제한했기 때문에 Animal 타입의 하위 타입은 Box, Box은 전달 할 수 없다.
    • 제네릭 타입, 제네릭 메서드에서는 사용할 수 없다.
profile
Java Back-End 2022.11.01 💻~ing

0개의 댓글