[공부 내용 정리]
제한된 타입 파라미터 (<T extends 최상위타입>)
타입 파라미터에 구체적인 타입을 제한하는 기능
public **<T extends 상위타입>** 리턴타입 메소드(매개변수, ...) {
}
→ 제한된 타입 파라미터를 선언하려면 타입 파라미터 뒤에 extends 키워드가 붙고 상위 타입을 명시하면 됨.
package chapter10;
public class Util {
public static <T extends Number> int compare(T t1, T t2) {
double value1 = t1.doubleValue();
double value2 = t2.doubleValue();
// compare : value1이 작다면 -1 리턴, value1이 크다면 1 리턴, 같다면 0 리턴.
return Double.compare(value1, value2);
}
}
→ Util.java
- 위 코드에서 doubleValue() 메소드는 숫자를 double 타입으로 변환함.
- Double.compare 메소드는 비교 메소드.
package chapter10;
public class BoundedTypeExample {
public static void main(String[] args) {
// int result = Util.compare("a", "b"); // 이렇게 쓰면 컴파일 오류. 타입으로 Number가 와야됨
int result = Util.compare(1, 2); // int -> Integer (자동 Boxing)
System.out.println(result);
int result2 = Util.compare(4.5, 4.5); // double -> Double (자동 Boxing)
System.out.println(result2);
}
}
→ BoundedTypeExample.java
와일드카드 타입<?>, <? extends …>, <? super ..>
제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드 카드를 3가지 형태로 사용할 수 있음.
ex) 수강생이 될 수 있는 4가지 클래스로 와일드카드 타입 실습을 해보자.
package chapter10;
public class Course<T> {
private String name;
private T[] students;
public Course(String name, int capacity) {
this.name = name;
students = (T[]) (new Object[capacity]);
//타입 파라미터로 배열을 생성하려면 new T[n] 형태로 배열을 생성할 수 없고 (T[]) (new Object[n])으로 생성해야한다.
}
public String getName() {
return name;
}
public T[] getStudents() {
return students;
}
public void add(T t) {
// 배열에 비어있는 부분을 찾아서 수강생을 추가하는 메소드
for (int i = 0; i < students.length; i++) {
if (students[i] == null) {
students[i] = t;
break;
}
}
}
}
→ Course.java
package chapter10;
import java.util.Arrays;
public class WildCardExample {
static void registerCourse(Course<?> course) {
// toString에 배열을 넣으면 배열의 요소들을 하나하나씩 출력해줌.
System.out.println(course.getName() + " 수강생: "+Arrays.toString(course.getStudents()));
}
static void registerStudentCourse(Course<? extends Student> course) {
System.out.println(course.getName() + " 수강생: "+Arrays.toString(course.getStudents()));
}
static void registerWorkerCourse(Course<? super Worker> course) { // super을 사용해 상위 클래스 지정(Worker와 Person 올 수 있음)
System.out.println(course.getName() + " 수강생: "+Arrays.toString(course.getStudents()));
}
public static void main(String[] args) {
// Person에는 하위 클래스 전부 들어갈 수 있음
Course<Person> personCourse = new Course<>("일반인 과정", 4);
personCourse.add(new Person("일반인"));
personCourse.add(new Worker("직장인"));
personCourse.add(new Student("학생"));
personCourse.add(new HighStudent("고등학생"));
// Worker에는 자기 자신 (Worker)만 들어갈 수 있음
Course<Worker> workerCourse = new Course<>("직장인 과정", 4);
workerCourse.add(new Worker("직장인"));
// Student에는 자기 자신(Student)와 하위 클래스인 HighStudent가 들어갈 수 있음.
Course<Student> studentCourse = new Course<>("학생 과정", 4);
studentCourse.add(new Student("학생"));
studentCourse.add(new HighStudent("고등학생"));
// HighStudent 에는 자기 자신 (HighStudent)만 들어갈 수 있음
Course<HighStudent> highStudentCourse = new Course<>("고등학생 과정", 4);
highStudentCourse.add(new HighStudent("고등학생"));
// 모든 클래스가 다 들어갈 수 있음!
registerCourse(personCourse);
registerCourse(workerCourse);
registerCourse(studentCourse);
registerCourse(highStudentCourse);
System.out.println("---------------------------------");
// registerStudentCourse는 Student와 HighStudent를 제외한 나머지 클래스를 넣으면 에러 발생.
// registerStudentCourse(personCourse);
// registerStudentCourse(workerCourse);
registerStudentCourse(studentCourse);
registerStudentCourse(highStudentCourse);
System.out.println("---------------------------------");
// registerWorkerCourse는 Person과 Worker를 제외한 나머지 클래스를 넣으면 에러 발생.
registerWorkerCourse(personCourse);
registerWorkerCourse(workerCourse);
// registerWorkerCourse(studentCourse);
// registerWorkerCourse(highStudentCourse);
}
}
→ WildCardExample.java
위 예시처럼
- 제네릭타입<?> : Unbounded Wildcards (제한 없음)
- 모든 클래스나 인터페이스 타입이 올 수 있음.
- 제네릭 타입<? extends 상위타입> : Upper Bounded Wildcards (상위 클래스 제한)
- 해당 클래스 기준 본인과 하위 타입만 올 수 있음.
- ex) registerStudentCourse에 Student, HighStudent만 들어가는 것 처럼
- 제네릭 타입<? super 하위타입> : Lower Bounded Wildcards (하위 클래스 제한)
- 해당 클래스 기준 본인과 상위 타입만 올 수 있음.
- ex) registerWorkerCourse에 Person과 Worker만 들어가는 것 처럼
→ 부모 제네릭 클래스 Product.java→ 자식 제네릭 클래스 ChildProduct.javapublic class Product<T, M> { private T kind; private M model; public T getKind() { return kind; } public M getModel() { return model; } public void setKind(T kind) { this.kind = kind; } public void setModel(M model) { this.model = model; } }
Daily Quiz
1. 제네릭에 대한 설명으로 틀린 것은 무엇입니까?
- 컴파일시 강한 타입 체크를 할 수 있다. O
- 타입 변환(casting)을 제거한다. O
- 제네릭 타입은 타입 파라미터를 가지는 제네릭 클래스와 인터페이스를 말한다. O
- 제네릭 메소드는 리턴 타입으로 타입 파라미터를 가질 수 없다. X
2. ContainerExample 클래스의 main() 메소드는 Container 제네릭 타입을 사용하고 있습니다. main() 메소드에서 사용하는 방법을 참고해서 Container 제네릭 타입을 선언해보세요.
public class ContainerExample {
public static void main(String[] args) {
Container<String> stringContainer = new Container<>();
stringContainer.set("홍길동");
String str = stringContainer.get();
Container<Integer> intContainer = new Container<>();
intContainer.set(6);
int value = intContainer.get();
}
}
→
package chapter10;
public class Container<T> {
private T name;
public void set(T name) {
this.name = name;
}
public T get() {
return name;
}
}
3. TwoContainerExample 클래스의 main() 메소드는 TwoContainer 제네릭 타입을 사용하고 있습니다. main() 메소드에서 사용하는 방법을 참고해서 TwoContainer 제네릭 타입을 선언해보세요.
public class TwoContainerExample {
public static void main(String[] args) {
TwoContainer<String, String> container = new TwoContainer<>();
container.set("홍길동", "도적");
String name = container.getKey();
String job = container.getValue();
TwoContainer<String, Integer> secondContainer = new TwoContainer<>();
secondContainer.set("홍길동", 35);
String name2 = secondContainer.getKey();
Integer age = secondContainer.getValue();
}
}
→
package chapter10;
public class TwoContainer<T, M> {
private T key;
private M value;
public void set(T key, M value) {
this.key = key;
this.value = value;
}
public T getKey() {
return key;
}
public M getValue() {
return value;
}
}
4. Util.getValue() 메소드는 첫 번째 매개값으로 Pair 타입과 하위 타입만 받고, 두 번째 매개값으로 키를 받습니다. 리턴값은 키값이 일치할 경우 Pair에 저장된 값을 리턴하고, 일치하지 않으면 null을 리턴하도록 getValue() 제네릭 메소드를 정의해보세요.
public class UtilExample {
public static void main(String[] args) {
Pair<String, Integer> pair = new Pair<>("홍길동", 35);
Integer age = Util.getValue(pair, "홍길동");
System.out.println(age);
ChildPair<String, Integer> childPair = new ChildPair<>("삼길동", 20);
Integer childAge = Util.getValue(pair, "이길동");
System.out.println(childAge);
// OtherPair는 Pair를 상속하지 않으므로 예외가 발생해야합니다.
/* OtherPair<String, Integer> otherPair = new OtherPair<>("삼길동, 20");
int otherAge = Util.getValue(otherPair, "삼길동");
System.out.println(otherAge); */
}
}
public class Pair<K, V> {
private K key;
private V value;
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
}
public class ChildPair<K, V> extends Pair<K, V> {
public ChildPair(K key, V value) {
super(key, value);
}
}
public class OtherPair<K, V> {
private K key;
private V value;
public OtherPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
->
package chapter10;
public class Util {
public static <K, V> V getValue(Pair<K, V> pair, String key) {
if (pair.getKey().equals(key)) {
return pair.getValue();
}
return null;
}
}
실행결과는 다음과 같습니다.
35
null
'Back-End > Java # 교육' 카테고리의 다른 글
[Java 교육] LIFO,FIFO 컬렉션/람다식 (다시 복습 필수 - 람다식) (1) | 2024.02.16 |
---|---|
[Java 교육] Collection/List/Set/Map (0) | 2024.02.15 |
[Java 교육] 자동 리소스 닫기/중간정리/제네릭 (1) | 2024.02.08 |
[Java 교육] 사용자 정의 예외/트랜잭션 (1) | 2024.02.07 |
[Java 교육] 예외처리/trycatch&finally (1) | 2024.02.06 |