백엔드 공부하기/TIL

230808 TIL : 제네릭 프로그래밍, 제네릭 메소드, 와일드카드..

개발중인 감자 2023. 8. 9. 01:42

📌 230808 TIL : 제네릭 프로그래밍, 제네릭 메소드 


제네릭 프로그래밍이란?

  • 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 방법을 제네릭이라고 한다.  데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있는 기술에 중점을 두어 재사용성을 높일 수 있는 프로그래밍 방식이다. 
  • <T>를 자료형 매개변수 대신 작성한다. 이 클래스를 사용하는 시점에 실제 사용할 자료형을 지정한다.
  • static 변수는 사용할 수 없는데, 이유는 자료형이 결정되기 전에 메모리에 적재될 수 없는 논리적이 이유 때문이다.
  • T: type, E : element, K : key, V: value : 여러 알파벳을 의미에 따라 사용이 가능하다.

 

제네릭을 사용하는 이유 

JDK 1.5 이전에는 여러 타입을 표현하기 위해서 Object 타입 변수를 사용해왔으나, Object 타입으로 받은 객체를 온전히 사용하기 위해서는 다운캐스팅을 일일이 해줘야하는 번거로움이 있었다. 하지만 제네릭 방법을 사용하면 일일이 캐스팅을 해줄 필요없이 바로 사용이 가능하다. 

 

✏️ 사용 예제

public abstract class Material {
    public abstract void doPrinting();
}

public class Plastic extends Material{
    public String toString() {
        return "재료는 plastic 입니다.";
    }

    @Override
    public void doPrinting() {
        System.out.println("Plastic printing");
    }
}

public class Powder extends Material{
    public String toString() {
        return "재료는 Powder 입니다.";
    }

    @Override
    public void doPrinting() {
        System.out.println("Powder printing");
    }
}

//제네릭 클래스, 단 Material 을 상속받은 클래스만 사용이 가능.
public class GenericPrinter<T extends Material> {

    private T material;

    @Override
    public String toString() {
        return material.toString();
    }

    public T getMaterial() {
        return material;
    }

    public void setMaterial(T material) {
        this.material = material;
    }
}

public class GenericPrinterTest {
    public static void main(String[] args) {
        GenericPrinter<Powder> powderGenericPrinter = new GenericPrinter<>();
        powderGenericPrinter.setMaterial(new Powder());
        Powder p = powderGenericPrinter.getMaterial(); //형변환 필요없음.
        System.out.println(powderGenericPrinter.toString());

        GenericPrinter<Plastic> plasticGenericPrinter = new GenericPrinter<>();
        plasticGenericPrinter.setMaterial(new Plastic());
        System.out.println(plasticGenericPrinter.toString());
        
    }
}

 

 

제네릭 메서드란

자료형 매개변수를 가지는 메서드의 매개변수나 반환 값으로 가지는 메서드는 자료형 매개변수가 하나 이상인 경우도 있다. 

그런 경우 제네릭 메서드를 사용해도된다. 또한 제네릭 메서드에는 static이 가능하다. 

제네릭 메서드의 기본형은 <T> 를 붙여 사용한다. 

예시 ) public <T> T getOneStudent(T id)

public class Student {
    static T name; //안됨.
    static <T> T getOneStudent(T id) { //됨.
        return id;
    }
}

위에서는 클래스의 제네릭 <T> 에서 설정된 타입을 받아와 반환 타입으로 사용할 뿐인 일반 메서드라면, 제네릭 메서드는 직접 메서드에 <T> 제네릭을 설정함으로서 동적으로 타입을 받아와 사용할 수 있는 독립적으로 운용 가능한 제네릭 메서드라고 이해하면 된다.

 

✏️ 사용 예제 (제네릭 메소드)

class FruitBox<T, U> {
    // 독립적으로운영되는 제네릭 메서드
    public <T, U> void printBox(T x, U y) {
        // 해당 매개변수의 타입 출력
        System.out.println(x.getClass().getSimpleName());
        System.out.println(y.getClass().getSimpleName());
    }
}
public class FruitBoxTest {
    public static void main(String[] args) {
        FruitBox<Integer, Long> box1 = new FruitBox<>();

        // 인스턴스화에 지정된 타입 파라미터 <Integer, Long>
        box1.printBox(1, 1);

        // 하지만 제네릭 메서드에 다른 타입 파라미터를 지정하면 독립적으로 운용 된다.
        System.out.println();
        box1.<String, Double>printBox("hello", 5.55);
        box1.printBox("hello", 5.55); // 생략 가능
    }
}

 

와일드 카드 제네릭 활용 예제 (GenericAnimalTest)

<? extends A> : A 와 A를 상속받는 클래스들만 자료형으로 제한. 

<? super A> : A와 A의 상위 클래스들만 자료형으로 제한. 

class LandAnimal { public void crying() { System.out.println("육지동물 아무개"); } }
class Cat extends LandAnimal { public void crying() { System.out.println("냐옹냐옹"); } }
class Dog extends LandAnimal { public void crying() { System.out.println("멍멍"); } }
class Pig extends LandAnimal { public void crying() { System.out.println("꿀꿀");} }
class Sparrow { public void crying() { System.out.println("짹짹"); } }

class AnimalList<T> {
    ArrayList<T> al = new ArrayList<>();

    public static void cryingAnimalList(AnimalList<? extends LandAnimal> list) {
        //와일드 카드 (? extends LandAnimal) 을 이용해 타입을 제한한 경우.
        for (int i = 0; i < list.size(); i++) {
            list.get(i).crying();
        }
    }

    void add(T animal) { al.add(animal); }
    T get(int index) { return al.get(index); }
    boolean remove(T animal) { return al.remove(animal); }
    int size() { return al.size(); }

}

public class GenericAnimalTest {

    public static void main(String[] args) {
        // // 제네릭 타입은 다형성 원리가 그대로 적용된다.
        AnimalList<LandAnimal> animalList = new AnimalList<>();
        animalList.add(new LandAnimal());
        animalList.add(new Cat());
        animalList.add(new Dog());
        animalList.add(new Pig());
        //animalList.add(new Sparrow()); //error!, LandAnimal을 상속받는 자료형만 들어갈 수 있음.

        AnimalList.cryingAnimalList(animalList);
    }

}