▶ 제네릭 메소드 이전 포스팅 글 참고
[JAVA] 제네릭(Generic)과 제네릭 메소드(Generic Method)
1. Object 타입 public class Box { public Object content; } 설계할 당시에 구체적인 타입이 지정이 안 됐을 때, 최상위 부모 클래스인 Object 나 Generic 타입으로 선언하는 방식을 주로 사용하게 된다. Object 타입
hyeonju0121.tistory.com
1. 제한된 타입 파라미터
public class Box<T> {
private T content;
content.________;
}
타입이 결정되지 않은 상태인 경우에는 Object 가 갖고 있는 메소드만 사용이 가능하다.
public class Box<Integer> {
private T content;
content._________;
}
Integer 타입으로 제한을 둔 경우, content는 Integer 타입의 메소드만 사용 가능하게 된다.
만약 경우에 따라서는 타입 파라미터를 대체하는 구체적인 타입으로 제한할 필요가 있다.
예를 들어서, 숫자 연산만 가능할 수 있도록 제네릭 메소드에 대체 타입으로 Number 또는 자식 클래스 (Byte, Short, Integer, Long, Double) 로 제한할 수 있다.
public <T extends 상위타입> 리턴타입 메소드 (매개변수,...) {...}
위와 같이, 타입 파라미터를 대체하는 구체적인 타입에 제한이 필요한 경우,
상위타입을 상속하는 자식 또는 구현 관계에 있는 타입만 대체할 수 있도록 정의를 할 수가 있다.
따라서, 상위타입을 상속하는 <T> 타입만 올 수 있으며, 이러한 타입 파라미터를 제한된 타입 파라미터 라고 한다.
숫자 연산만 가능할 수 있도록 제네릭 메소드에 대체 타입으로 Number 또는 자식 클래스 (Byte, Short, Integer, Long, Double) 로 제한을 둔 경우
public <T extends Number> 리턴타입 메소드 (매개변수, ....) {...}
위 코드는 Number 클래스를 상속하는 타입만 올 수 있도록 제한을 둔 것이다.
즉, Number 본인 클래스 또는 자식 클래스들이 <T> 타입으로 대체할 수 있다.
Number (Java SE 17 & JDK 17)
All Implemented Interfaces: Serializable Direct Known Subclasses: AtomicInteger, AtomicLong, BigDecimal, BigInteger, Byte, Double, DoubleAccumulator, DoubleAdder, Float, Integer, Long, LongAccumulator, LongAdder, Short The abstract class Number is the supe
docs.oracle.com
Number 클래스의 자식 클래스들을 확인해보면, Integer, Long, Double .. 등등 대부분 Wrapper 클래스 등이 있다.
따라서, <T> 타입에는 Number 클래스의 자식 클래스만 대체 가능하다.
1.1 제한된 타입 파라미터 제네릭 메소드 예제
public class GenericExample {
// 제한된 타입 파라미터를 갖는 제네릭 메소드
// Number 클래스를 상속하는 타입 (즉, 자식 객체만 T 타입으로 올 수 있다.)
public static <T extends Number> boolean compare(T t1, T t2) {
// T 타입 출력
// .getClass() : 해당 타입의 클래스 이름 갖고오기
// .getSimpleName() : 해당 클래스의 이름 리턴
System.out.println("compare(" + t1.getClass().getSimpleName() +
", " + t2.getClass().getSimpleName() + ")");
// Number 클래스의 메소드 사용
double v1 = t1.doubleValue();
double v2 = t2.doubleValue();
return (v1 == v2);
}
public static void main(String[] args) {
// 제네릭 메소드 호출
boolean result1 = compare(10, 20);
System.out.println("result1 = " + result1);
System.out.println();
// 제네릭 메소드 호출
boolean result2 = compare(4.5, 4.5);
System.out.println("result2 = " + result2);
}
}
compare 메소드 안에서 doubleValue() 메소드를 사용할 수 있는 이유는
타입 파라미터가 Number 타입으로 제한되면서 최상위 부모 클래스인 Object의 메소드 뿐만 아니라
Number가 갖고 있는 메소드도 사용할 수 있기 때문이다.
main 실행 메소드에서 compare(10, 20) 으로 제네릭 메소드를 호출하게 되면,
해당 들어온 값 (T t1, T t2) 가 Integer 타입으로 자동 박싱 된다.
2. 와일드카드 타입 파라미터
제네릭 타입을 매개값이나 리턴 타입으로 사용할 때, 타입 파라미터로 ? (와일드카드) 를 사용할 수 있다.
? 는 범위에 있는 모든 타입으로 대체할 수 있다는 표시이다.
public class Person {
}
class Worker extends Person {
}
class Student extends Person {
}
class HighStudent extends Student {
}
class MiddleStudent extends Student {
}
예를 들어서, 위와 같은 상속 관계가 있다고 가정했을 때
타입 파라미터의 대체 타입으로 Student 와 자식 클래스인 HighStudent 와 MiddleStudent만 가능하도록 매개변수를 다음과 같이 선언할 수 있다.
- Student 타입 기준으로 그 하위타입만 대입될 수 있다.
리턴타입 메소드명 (제네릭타입 <?extends Student> 변수) {...}
- 반대로, Worker 타입을 기준으로 그 상위타입만 대입될 수 있다.
리턴타입 메소드명 (제네릭타입 <?super Worker> 변수) {...}
- 모든 타입의 객체가 대입될 수 있다.
리턴타입 메소드명 (제네릭타입 <?> 변수) {...}
즉, ? super R 은 R을 포함하고 그 위에 부모 클래스가 올 수 있고,
? extends V 는 V 를 포함하고 그 자식 클래스들이 올 수 있다.
제네릭 메소드에서 제한된 타입 파라미터를 정의할 경우에는 리턴 타입 앞에 정의했었다.
하지만, 와일드카드 타입 파라미터는 매개변수에 정의한다.
즉, 타입 파라미터 중에서도 여기에 속한 것만 매개변수에 올 수 있다는 뜻이다.
2.1 와일드카드 타입 파라미터 예제
public class Course {
// 모든 사람이면 신청 가능 (Person, Worker, Student, HighStudent, MiddleStudent)
// <?> 어떤 타입이든 T 에 대입될 수 있음
public static void registerCourse1(Applicant<?> applicant) {
// 들어오는 타입의 클래스 이름 출력
System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course1 (모든 사람 신청 가능) 을 신청했습니다.");
}
// Student 타입이거나, 하위 타입만 신청 가능 (Student, HighStudent, MiddleStudent)
// <? extends Student>
public static void registerCourse2(Applicant<? extends Student> applicant) {
// 들어오는 타입의 클래스 이름 출력
System.out.println(applicant.kind.getClass().getSimpleName() +
"이(가) Course2 (학생만 신청 가능) 을 신청했습니다.");
}
// Worker 타입이거나, 상위 타입만 신청 가능 (Person, Worker)
// <? super Worker>
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) {
// 모든 사람이면 신청할 수 있음
/*
* Applicant<Person> app1 = new Applicant<Person>(new Person());
* Course.registerCourse1(app1);
*/
Course.registerCourse1(new Applicant<Person>(new Person())); // Applicant<Person> -> 생성자 호출 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()));
System.out.println();
/*
* Course.registerCourse2(new Applicant<Worker>(new Worker()));
* The method registerCourse2(Applicant<? extends Student>)
* in the type Course is not applicable for the arguments (Applicant<Worker>)
* Student 타입의 자식 타입만 올 수 있는데, Worker 타입은 자식이 아니라서 매개변수로 올 수 없다!
*/
Course.registerCourse2(new Applicant<Student>(new Student()));
Course.registerCourse2(new Applicant<HighStudent>(new HighStudent()));
Course.registerCourse2(new Applicant<MiddleStudent>(new MiddleStudent()));
System.out.println();
Course.registerCourse3(new Applicant<Worker>(new Worker()));
Course.registerCourse3(new Applicant<Person>(new Person()));
}
}