토자맨 2024. 11. 26. 19:59

제네릭

결정되지 않은 타입을 파라미터로 처리하고 실제 사용할 때 파라미터를 구체적인 타입으로 대체시키는 기능

  • 아래와 같이 Box 클래스를 선언하려고 하는데 필드의 타입을 무엇으로 할지 결정되지 않은 경우 모든 클래스의 조상인 Object 타입으로 선언할 수 있다.
  • 이렇게 하면 강제 타입 변환을 통해 어떤 객체든 대입될 수 있다.
  • 해당 필드를 얻을 때 어떤 타입인지 알 수 없으므로 강제 타입 변환을 통해 얻어야 한다.
  • 하지만 모든 클래스를 대상으로 조사해서 얻는 것은 불가능하기 때문에 Object 타입을 이용하는 것은 적절하지 않다.
public class Box {
    public ? content;
}
  • 이런 경우 사용하는 것이 제네릭이다. 아래 예시로 보자
  • 선언부에 <>부모를 붙이고 그 안에 타입 파라미터를 위치한다.
    • <T>: T가 타입 파라미터임을 뜻하는 기호로 타입이 필요한 자리에 T를 사용하면 된다.
  • 객체를 생성할 때 타입을 대입하면 해당 타입으로 대체되어 객체가 생성된다.(T -> String)
  • 주의할 점은 타입 파라미터는 클래스 및 인터페이스이므로 타입 변수인 int, char 등은 사용이 불가능하다.
  • Integer, String 등만 사용 가능하다.
public class Box <T> {
    public T content;
}
// <T>: T가 타입 파라미터임을 뜻하는 기호. 타입이 필요한 자리에 T를 사용할 수 있음을 알려줌

Box<String> box = new Box<String>();
box.content = "안녕하세요";
String content = box.content;    // 강제 타입 변환 필요 없이 "안녕하세요"를 바로 얻을 수 있음
  • 동일 타입으로 호출한다면
    -Box<String> box = new Box<String>();가 아닌
    • Box<String> box = new Box<>();로 생략해도 된다.

제네릭 타입

  • 클래스
// 제네릭 타입
public class Product<K, M> {    // 타입 파라미터로 K, M 정의
    // 필드
    private K kind;        // 타입 파라미터를 필드 타입으로 사용
    private M model;

    public K getKind() { return this.kind; }
    public M getModel() { return this.model; }
    public void setKind(K kind) { this.kind = kind; }
    public void setModel(M model) { this.model = model; }
}


public class Tv {}

public class Car {}


public class GenericExample {
    public static void main(String[] args) {
        // K는 Tv로 대체, M은 String으로 대체
        Product<Tv, String> product1 = new Product<>();

        // Setter 매개값은 반드시 Tv와 String을 제공
        product1.setKind(new Tv());
        product1.setModel("스마트Tv");

        // Getter 리턴값은 Tv와 String이 됨
        Tv tv = product1.getKind();
        String tvModel = product1.getModel();


        Product<Car, String> product2 = new Product<>();

        product2.setKind(new Car());
        product2.setModel("SUV자동차");

        Car car = product2.getKind();
        String carModel = product2.getModel();
    }
}
  • 인터페이스
public interface Rentable<P> {
    P rent();    // 타입 파라미터 P를 리턴 타입으로 사용
}

public class Home {
    public void turnOnLight() {
        System.out.println("전등을 켭니다.");
    }
}

public class Car {
    public void run() {
        System.out.println("자동차가 달립니다.");
    }
}

public class HomeAgency implements Rentable<Home> {
    @Override
    public Home rent() {
        return new Home();    // 리턴 타입이 반드시 Home이어야 함
    }
}

public class CarAgency implements Rentable<Car> {
    @Override
    public Car rent() {
        return new Car();    // 리턴 타입이 반드시 Car여야 함
    }
}

public class GenericExample {
    public static void main(String[] args) {
        HomeAgency homeAgency = new HomeAgency();
        Home home = homeAgency.rent();
        home.turnOnLight();

        CarAgency carAgency = new CarAgency();
        Car car = carAgency.rent();
        car.run();
     }
 }
public class Box <T> {
    public T content;

    // Box의 내용물이 같은지 비교
    public boolean compare(Box<T> other) {
        boolean result = content.equals(other.content);
        return result;
    }
}

public class GenericExample {
    public static void main(String[] args) {
        Box box1 = new Box();
        box1.content = "100";

        Box box2 = new Box();
        box2.content = "100";

        Box box3 = new Box();
        box3.content = 100;

        boolean result1 = box1.compare(box2);
        System.out.println(result1);            // true

        boolean result2 = box1.compare(box3);
        System.out.println(result2);            // false
    }
}

제네릭 메소드

메소드 선언부에 타입 파라미터를 정의된다.

  • public <A, B, ...> 리턴타입 메소드명(매개변수, ...) { ... }
public class Box<T> {
    // 필드
    private T t;


    // Getter 메소드
    public T get() {
        return t;
    }

    // Setter 메소드
    public void set(T t) {
        this.t = t;
    }
}

public class GenericExample {
    // 매개변수, 리턴에서 타입 파라미터 사용
    public static <T> Box<T> boxing(T t) {        // 타입 파라미터 T 정의
        Box<T> box = new Box<T>();
        box.set(t);
        return box;
    }

    public static void main(String[] args) {
        // 제네릭 메소드 호출
        Box<Integer> box1 = boxing(100);        // T를 Integer로 대체
        int intValue = box1.get(); // Integer 반환 -> int 자동 언박싱
        System.out.println(intValue);    // 100

        // 제네릭 메소드 호출
        Box<String> box2 = boxing("홍길동");        // T를 String으로 대체
        String strValue = box2.get();
        System.out.println(strValue);    // 홍길동
    }
}

제한된 타입 파라미터

모든 타입으로 대체할 수 없고, 특정 타입과 자식 또는 구현 관계에 있는 타입만 대체할 수 있는 타입 파라미터

  • 예를 들어 숫자를 연산하는 제네릭 메소드는 대체 타입으로 Number 또는 자식 클래스(Byte, Short, Integer, Long, Double)로 제한할 필요가 있다.
  • public <T extends 상위타입> 리턴타입 메소드(매개변수, ...) { ... }
public class GenericExample {
    // 제한된 타입 파라미터를 갖는 제네릭 메소드
    public static <T extends Number> boolean compare(T t1, T t2) {
        // T의 타입 출력
        System.out.println("compare(" + t1.getClass().getSimpleName() + ", " + t2.getClass().getSimpleName() + ")");

        // Number의 메소드 사용
        double v1 = t1.doubleValue();    // Number 타입의 doubleValue() 메소드 호출
        double v2 = t2.doubleValue();

        return (v1 == v2);
    }

    public static void main(String[] args) {
        // 제네릭 메소드 호출
        boolean result1 = compare(10, 20);        // T를 Integer 타입으로 대체
        System.out.println(result1);
        System.out.println();

        // 제네릭 메소드 호출
        boolean result2 = compare(4.5, 4.5);    // T를 Double 타입으로 대체
        System.out.println(result2);
    }
}

/*
compare(Integer, Integer)
false

compare(Double, Double)
true
*/

와일드카드 타입 파라미터

제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터로 ?(와일드카드) 사용 가능

  • 리턴타입 메소드명(제네릭타입<? extends Student> 변수) { ... }
    • Student와 자식 클래스만 가능하도록 제한(하위로)
  • 리턴타입 메소드명(제네릭타입<? super Worker> 변수) { ... }
    • Worker와 Person만 가능하도록 제한(상위로)
  • 리턴타입 메소드명(제네릭타입<?> 변수) { ... }
    • 어떤 타입이든 가능

public class Person {}
    class Worker extends Person {}
    class Student extends Person {}
    class HighStudent extends Student {}
    class MiddleStudent extends Student {}

    public class Applicant<T> {
        public T kind;

        public Applicant(T kind) {
            this.kind = kind;
        }
    }
public calss Applicant<T> {
    public T kind;

    public Applicant(T kind) {
        this.kind = kind;
    }
}
public class Course {
    // 모든 사람이 등록 가능
    public static void registerCourse1(Applicant<?> applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course1 등록");
    }

    // 학생만 등록 가능
    public static void registerCourse2(Applicant<? extends Student> applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course2 등록");
    }

    // 직장인 및 일반인만 등록 가능
    public static void registerCourse3(Applicant<? super Worker> applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course3 등록");
    }
}
public class GenericExample {
    public static void main(String[] args) {
        // 모든 사람이 신청 가능
        Course.registerCourse1(new Applicant<Person>(new Person()));
        Course.registerCourse1(new Applicant<Worker>(new Worker()));
        Course.registerCourse1(new Applicant<Student>(new Student()));
        Course.registerCourse1(new Applicant<HighStudent>(new HighStudent()));
        Course.registerCourse1(new Applicant<MiddleStudent>(new MiddleStudent()));

        // 학생만 신청 가능
        // Course.registerCourse2(new Applicant<Person>(new Person()));
        // Course.registerCourse2(new Applicant<Worker>(new Worker()));
        Course.registerCourse2(new Applicant<Student>(new Student()));
        Course.registerCourse2(new Applicant<HighStudent>(new HighStudent()));
        Course.registerCourse2(new Applicant<MiddleStudent>(new MiddleStudent()));

        // 직장인 및 일반인만 신청 가능
        Course.registerCourse3(new Applicant<Person>(new Person()));
        Course.registerCourse3(new Applicant<Worker>(new Worker()));
        // Course.registerCourse3(new Applicant<Student>(new Student()));
        // Course.registerCourse3(new Applicant<HighStudent>(new HighStudent()));
        // Course.registerCourse3(new Applicant<MiddleStudent>(new MiddleStudent()));
    }
}