JAVA/BASIC

JAVA Generic 제네릭 (2) / 제한된 타입 파라미터, 와일드카드 타입, 제네릭 타입의 상속과 구현

유혁스쿨 2023. 3. 12. 02:06
728x90
반응형

제한된 타입 파라미터 (<T extends 최상위 타입>)

타입 파라미터에 지정되는 구체적인 타입을 제한할 필요가 종종 있다.

숫자를 연산하는 제네릭 메소드는 매개값으로 Number타입 또는 하위 클래스타입(Byte, Short, Integer, Long, Double, Float)의 인스턴스만 가져와야 한다.

이것이 제한된 타입 파라미터 (Bounded Type Parameter)가 필요한 이유이다.

제한된 타입 파라미터를 선언하려면 타입 파라미터 뒤에 extends 키워드를 붙이고 상위 타입을 명시하면 된다.

상위 타입은 클래스 뿐만아니라 인터페이스도 가능하다.

인터페이스라고 해서 implements를 사용하지 않는다.

public <T extends 상위타입> 리턴타입 메소드(매개변수, ...) {...}

타입 파라미터에 저장되는 구체적인 타입은 상위 타입이거나 상위 타입의 하위 또는 구현 클래스만 가능하다.

주의할 점은 메소드의 중괄호 {} 안에서 타입 파라미터 변수로 사용 가능한 것은 상위 타입의 멤버(필드, 메소드)로 제한된다.

하위 타입에만 있는 필드와 메소드는 사용할 수 없다.

 

다음은 숫자 타입만 구체적인 타입으로 갖는 제네릭 메소드 compare() 이다. 두 개의 숫자 타입을 매개값으로 받아 차이를 리턴한다.

public <T extends Number> int compare(T t1, T t2) {
    double v1 = t1.doubleValue(); // Number의 doubleValue() 메소드 사용
    double v2 = t2.doubleValue(); // Number의 doubleValue() 메소드 사용
    return Double.comapre(v1, v2);
}

doubleValue() 메소드는 Number 클래스에 정의되어 있는 메소드로 숫자를 double 타입으로 변환한다.

Double.comapre() 메소드는 첫 번째 매개값이 작으면 -1을, 같으면 0을, 크면 1을 리턴한다.

 

[ Util.java ] 제네릭 메소드 compare() 정의

public class Util {
    public static <T extends Number> int compare(T t1, T t2) {
        double v1 = t1.doubleValue();
        double v2 = t2.doubleValue();
        return Double.compare(v1, v2);
    }
}

[ BoundedTypeParameterExample.java ] 제네릭 메소드 compare() 호출

public class example {
    public static void main(String[] args) {
//        String str = Util.compare("a", "b"); // String은 Number 타입이 아니므로 오류발생.
        int result1 = Util.compare(10, 20); //int 20 -> Integer AutoBoxing
        System.out.println("result1 = " + result1);

        int result2 = Util.compare(4.5, 3); //double 4.5 -> Double AutoBoxing
        System.out.println("result1 = " + result2);
    }
}

result1의 인자값 10과 20 그리고 result2의 두번째 인자값 3은 제네릭 타입 T를 상속한 최상위 타입인 Number에 의해 하위 클래스인 Integer Wrapper클래스타입으로 AutoBoxing 된다.

result2의 첫번재 인자값 4.5는 Number의 하위 클래스 인스턴스인 Double Wrapper 클래스타입으로 AutoBoxing 된다.

 

와일드카드 타입(<?>, <? extends ...>, <? super ...>)

코드에서 ?를 일반적으로 와일드카드(wildcard) 라고 부른다.

제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드카드를 다음과 같이 세 가지 형태로 사용할 수 있다.

 

· 제네릭타입<?> : Unbounded Wildcards(제한 없음)

타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다.

· 제네릭타입<? extends 상위타입> : Upper Bounded Wildcards(상위 클래스 제한)

타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 하위 타입만 올 수 있다.

· 제네릭타입<? super 하위타입> : Lower Bounded Wildcards(하위 클래스 제한)

타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 상위 타입이 올 수 있다.

 

[ Course.java ] 제네릭 타입

class Course<T> {
    private String name;
    private T[] students;

    public Course(String name, int capacity) {
        this.name = name;
//        this.student = student;
        this.students = (T[]) (new Object[capacity]);
    }

    public String getName() {
        return name;
    }

    public T[] getStudent() {
        return students;
    }

    public void add(T t) {
        for (int i = 0; i < students.length; i++) {
            if (students[i] == null) {
                students[i] = t;
                break;
            }
        }
    }
}

제네릭타입 Course는 과정 클래스로 과정  이름과 수강생을 저장할 수 있는 배열을 가지고 있다.

타입 파라미터 T가 적용된 곳은 수강생 타입 부분이다.

 

수강생이 될 수 있는 타입은 다음과 같다.

Person의 하위 클래스로 Worker와 Student가 있고, Student의 하위 클래스로 HighStudent가 있다.

 

· Course<?>

수강생은 모든 타입(Person, Worker, Student, HighStudent)이 될 수 있다.

· Course<? extends Student>

수강생은 Student와 HighStudent만 될 수 있다.

· Course<? super Worker>

수강생은 Worker와 Person만 될 수 있다.

public class WildCardExample {
    public static void registerCourse(Course<?> course) { //모든 과정
        System.out.println(course.getName() + "수강생 : " + Arrays.toString(course.getStudents()));
    }
    public static void registerCourseStudent(Course<? extends Student> course) { // 학생 과정
        //System.out.println(course.getName() + "수강생 : " + Arrays.toString(course.getStudents()));
        System.out.println(course.getName() + "수강생 : " + Arrays.toString(new Student[][]{course.getStudents()}));

    }
    public static void registerCourseWorker(Course<? super Worker> course) { // 직장인과 일반인 과정
        System.out.println(course.getName() + "수강생 : " + Arrays.toString(course.getStudents()));
    }
    public static void main(String[] args) {
        /* 일반인 과정 생성 */
        Course<Person> personCourse = new Course<Person>("일반인 과정", 5);
        personCourse.add(new Person("일반인"));
        personCourse.add(new Worker("직장인"));
        personCourse.add(new Student("학생"));
        /* 직장인 과정 생성 */
        Course<Worker> workerCourse = new Course<Worker>("직장인 과정", 5);
        workerCourse.add(new Worker("일반인"));
        /* 학생 과정 생성 */
        Course<Student> studentCourse = new Course<Student>("학생 과정", 5);
        studentCourse.add(new Student("학생"));
        studentCourse.add(new HighStudent("고등학생"));
        /* 고등학생 과정 생성 */
        Course<HighStudent> highStudentCourse = new Course<HighStudent>("고등학생 과정", 5);
        highStudentCourse.add(new HighStudent("고등학생"));
    }
    /* 모든 과정 등록 가능 */
    registerCourse(personCourse);
    registerCourse(workerCourse);
    registerCourse(studentCourse);
    registerCourse(highStudentCourse);
    /* 학생 과정만 등록 가능 */
    registerCourseStudent(studentCourse);
    registerCourseStudent(highStudentCourse);
    /* 직장인과 일반인 과정만 등록 가능 */
    registerCourseWorker(personCourse);
    registerCourseWorker(workerCourse);
}

registerCourseXXX() 메소드의 매개값으로 와일드카드 타입을 사용하였다.

registerCourse() : 모든 수강생이 들을 수 있는 과정 등록

registerCourseStudent() : 학생만 들을 수 있는 과정 등록

registerCourseWorker() : 직장인만 들을 수 있는 과정 등록


책을 찾아봐도 Person, Worker, Student, HighStudent class는 없는데, 카페에 있는 건지 아니면 앞에서 있었는지 책이 두꺼워서 다시 확인은 못했다. class는 간단하게 만들어서 하면 되기는 하지만, 초보자들에게는 당황스러울 수 있는 부분

초급대상이긴 하지만 generic 이야기하면서, covariance/contravariance라는 게 있다 정도는 이야기 해줘야할 거 같은데… 이 부분은 뭐가 맞는지는 잘 모르겠음. 하지만 용어라도 알아야 나중에 찾아볼 수 있지 않을까?

 

https://www.hanbit.co.kr/media/community/review_view.html?hbr_idx=6012 

 

이것이 자바다

“결론은 ‘자바’다. 가장 중요한 프로그래밍 언어를 하나 배워야 한다면,” 자바는 현재 웹 애플리케이션 개발에 가장 많이 사용되는 언어이고, 모바일 기기용 소프트웨어 개발에도 널리 사

www.hanbit.co.kr


제네릭 타입의 상속과 구현

제네릭 타입도 다른 타입과 마찬가지로 부모 클래스가 될 수 있다.

다음은 Product<T, M> 제네릭 타입을 상속해서 ChildProduct<T, M> 타입을 정의하는 코드이다.

public class ChildProduct<T, M> extends Product<T, M> {...}

자식 제네릭 타입은 추가적으로 타입 파라미터를 가질 수 있다.

다음은 세 가지 타입 파라미터를 가진 자식 제네릭 타입을 선언한 것이다.

public class ChildProduct<T, M, C> extends Product<T, M> {...}

 

[ Product.java ] 부모 제네릭 클래스

class Product <T, M> {
    private T kind; //종류
    private M model; //모델

    public T getKind() {return kind;}
    public void setKind(T kind) {this.kind = kind;}

    public M getModel() {return model;}
    public void setModel(M model) {this.model = model;}
}

class Tv {}

[ ChildProduct.java ] 자식 제네릭 클래스

public class ChildProduct<T, M, C> extends Product<T, M> {
    private C company;
    public C getCompany() {return this.company;}
    public void setCompany(C company) {this.company = company;}
}

 

제네릭 인터페이스를 구현한 클래스도 제네릭 타입이 된다.

[ Storage.java ] 제네릭 인터페이스

public interface Storage<T> {
    public void add(T item, int index);
    public T get(int index);
}

[ StorageImpl.java ] 제네릭 구현 클래스

public class StorageImpl<T> implements Storage<T> {
    private T[] array;
    
    public StorageImpl(int capacity) {
        this.array = (T[]) (new Object[capacity]);
    }
    @Override
    public void add(T item, int index) {
        array[index] = item;
    }
    @Override
    public T get(int index) {
        return array[index];
    }
}

 

ChildProduct<T, M, C>와 StorageImpl<T> 클래스의 사용 방법

public class Example{
    public static void main(String[] args) {
        ChildProduct<Tv, String, String> product = new ChildProduct<>();
        product.setKind(new Tv()); //Product 클래스의 setKind() 메소드 호출
        product.setModel("SmartTV"); //Product 클래스의 setModel() 메소드 호출
        product.setCompany("Samsung"); //ChildProduct 클래스의 setCompany() 메소드 호출
        
        Storage<Tv> storage = new StorageImpl<Tv>(100); // 업캐스팅(다형성)
        storage.add(new Tv(), 0);
        Tv tv = Storage.get(0);
    }
}

 

 


번외

타입 파라미터는 인스턴스 생성이 불가능하다.

class MemberDAOImpl {
    public static void main(String[] args) {

        MemberVO memberVO = null;
        List<MemberVO> mList = memberList(memberVO);
    }
    public static <T extends objectVO> List<T> memberList(T t) {
        List<T> mList = new ArrayList<>();
        T voObject = (T) new Object(); // Type parameter 'T' cannot be instantiated directly - 제네릭의 타입 파라미터는 인스턴스 생성 불가능
        voObject.setExValue(3);
        int exValue = voObject.getExValue();
        System.out.println(exValue); // 3 출력
        mList.add(t);
        return mList;
    }
}
abstract class objectVO {
    public int exValue;
    public abstract int getExValue();
    public abstract void setExValue(int exValue);
}
class MemberVO extends objectVO{
    private int exValue;

    @Override
    public int getExValue() {
        return exValue;
    }

    @Override
    public void setExValue(int exValue) {
        this.exValue = exValue;
    }
}
728x90
반응형