제네릭
컴파일 시 강한 타입 체크 - 컴파일러가 에러를 사전에 방지하게 한다.
타입변환을 제거한다 - 애초에 담아올 타입을 국한함으로써 불필요한 타입 변환을 방지한다.
제네릭 타입( class<T>, interface<T> )
타입을 파라미터<알파벳>로 가지는 클래스와 인터페이스
타입 파라미터는 일반적으로 대문자 알파벳 한 글자로 표현한다.
클래스를 설계할 때 구체적인 타입을 명시하지 않고, 타입 파라미터로 대체했다가 실제 클래스가 사용될 때 구체적인 타입을 지정하여 사용한다.
제네릭 타입의 클래스, 인터페이스를 만드는 법
① 클래스 또는 인터페이스 이름 뒤에 <> 부호가 붙고 <> 사이에 타입 파라미터가 위치한다.
② 클래스 내부 필드, 메소드, 생성자를 선언할 때 구체적인 타입 대신 클래스 선언시에 사용한 타입 파라미터를 사용한다.
class Car<T> {
T model //필드 void setModel(T model){ this.model = model;} T getModel { return model; } }
interface Box<T, M> { }
③ 해당 클래스를 객체생성 시 실제 타입을 지정한다.
public static void main(String[] args){
Car<String> car = new Car<String>();
//Car<String> car = new Car<>(); // < >안 생략가능
//Car car = new Car<String>(); //리턴타입에서 타입 파라미터 생략 가능
}
④ 참조를 통해 접근, 대개 위에서 지정한 타입을 매개변수로 setter 사용, getter를 통해 얻은 값 또한 리턴타입이 일치
car.set("전기차");
String model = car.get();
멀티 타입 파라미터
Product<T , U> product = new Product<>();
비제네릭 타입의 문제점 예시
Object를 쓴 이유는 필드에 모든 종류의 객체를 저장하고 싶어서이다 (자동형변환을 통해 저장되고, 강제형변환을 통해 원래 타입의 객체를 얻을 수 있다)
Object를 통해서도 여러 타입의 객체를 필드에 저장할 수 있지만, 불필요한 형변환 필요하며 하나의 Box 객체 필드 안에 저장되는 데이터의 형이 전부 달라 후에 예외발생 여지를 남긴다.
public class BoxExample {
public static void main(String[] args) {
Box box = new Box();
box.set("홍길동");
String name = (String) box.get();
System.out.println(name);
box.set(new Apple());
Apple apple = (Apple) box.get();
System.out.println(apple);
}
}
class Box{
private Object object; //어떤 것도 넣을 수 있도록 최상위 타입
public void set(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
class Apple{
}
제네릭 타입으로 전환 예시
public class BoxExample {
public static void main(String[] args) {
Box<String> box = new Box<String>();
box.set("홍길동");
// String name = (String) box.get();
String name = box.get();
System.out.println(name);
Box<Apple> box2 = new Box<Apple>();
box2.set(new Apple());
Apple apple = box2.get();
System.out.println(apple);
}
}
class Box<T>{
private T t; //어떤 것도 넣을 수 있도록 최상위 타입
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
class Apple{
}
객체를 생성할 때마다, 필드는 같은데 타입이 달라질때 제네릭 타입을 쓰면 좋다
ex. A회사 kind String model String
B회사 kind Integer model String
C회사 kind Student model Person
public class ProductExample {
public static void main(String[] args) {
}
}
class Product<K,M>{
private K kind;
private M model;
public Product() {}
public Product(K kind, M model) {
this.kind = kind;
this.model = model;
}
void setKind(K kind) {
this.kind = kind;
}
void setModel(M model) {
this.model = model;
}
String getKind() {
return kind;
}
Integer getModel() {
return model;
}
}
제네릭 메소드
<T,R> R method(T t)
매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드
리턴 타입 앞에 <T>를 추가하고 리턴 타입과 매개 타입으로 타입 파라미터T를 사용한다
제네릭 메소드 선언 방법
public <타입파라미터, ..> 리턴타입 메소드명(매개변수, ...) { ... }
Box 클래스의 메소드 boxing ---> public <T> Box<T> boxing(T t) { ...}
제네릭 메소드 두 가지 호출 방법
1. 리턴타입 변수 = <구체적타입> 메소드명(매개값) //명시적으로 구체적 타입 지정
Box<Integer> box = <Integer> boxing(100);
2. 리턴타입 변수 = 메소드명(매개값); //매개값을 보고 구체적 타입 추정 (더욱 간단)
Box<Integer> box = boxing(100);
예시
Util은 공유객체, Util의 메소드 중 하나로 Box객체를 만들고 필드를 초기화해, 객체 참조를 리턴하는 메소드를 만들 수 있다.
굳이 Util을 한번 더 거치는 이유는?
Box 말고 다양한 클래스들이 있을 때 메인 클래스에서 일일이 해당 클래스를 객체화-초기화-내부의 멤버를 이용하려면 코드가 복잡해짐
즉 가독성을 위해 객체 생성 후 참조를 바로 리턴하는 메소드를 따로 만든다.
public class BoxingMethodExample {
public static void main(String[] args) {
//정적메소드이기 때문에 클래스명.메소드명으로 호출
Box<Integer> boxResult = Util.boxing(10); // new Integer(10) 같음
//10을 호출하는법 1 : Box클래스에서 오버라이딩한 toString
System.out.println(boxResult.toString());
//10을 호출하는법 2 : getT()
System.out.println(boxResult.getT());
// 문제 : carRun 메소드 호출하기 T는 String => 소나타 | V는 Integer ==>5000
// 출력문을 carRun 메소드 안에 넣음
Car<String, Integer> carResult = Util.carRun("소나타", 5000);
Car<Integer, Boolean> carResult2 = Util.carRun(900, true);
//문제 : goProduct메소드 호출하기 A-매니저 이름 "홍길동" B-성별 true C-20 나이
Product<String, Boolean, Integer> productResult = Util.goProduct("홍길동", true, 20);
System.out.println(productResult); //Product에서 toString 오버라이딩
}
}
// 제네릭 메소드 생성
class Util {
// 박스
public static <T> Box<T> boxing(T t) {
Box<T> box = new Box<T>(); //Box 객체생성
box.set(t); //Box의 set() 호출
return box; //Box 참조 리턴
}
// 만능자동차
public static <T,V> Car<T,V> carRun(T t, V v) {
Car<T,V> car = new Car<T,V>(t,v); //Car객체 생성
System.out.println(car.getT1() + " " + car.getV1()); //car의 t1, t2 얻어 출력
return car; //car 참조 리턴
}
// Prodcut를 리턴타입으로 쓰는 메소드 만들기 goProduct
// 타입파라미터 리턴타입 메소드명(매개값) 순
public static <A, B, C> Product<A, B, C> goProduct(A a, B b, C c) {
Product<A, B, C> product = new Product<A, B, C>(a,b,c); //객체 생성
return product;
}
}
class Box<T> {
T t;
void set(T t) { this.t = t; }
T getT() { return t; }
@Override
public String toString() {return t.toString();} //메소드 안의 toString은 Object클래스의 메소드
}
class Car<T, V> {
T t1;
V v1;
Car(T t, V v){ this.t1 = t; this.v1 = v;}
public T getT1() { return t1;}
public void setT1(T t1) { this.t1 = t1; }
public V getV1() { return v1;}
public void setV1(V v1) {this.v1 = v1;}
}
class Product<A, B, C> {
A a;
B b;
C c;
Product(A a, B b, C c){ this.a = a; this.b = b; this.c = c; }
public A getA() { return a; }
public B getB() { return b; }
public C getC() { return c; }
@Override
public String toString() { return getA() +" " + getB() +" "+ getC(); } //" " 넣어 문자열로 만듦
}

public class GenericMethodEx {
public static void main(String[] args) {
Box<Integer,Double> box = Util.boxig(new Integer(10), new Double(20.5));
Util util = new Util();
util.running(new Boolean(true), new Integer(90), new String("홍길동"));
Car car = util.running(new String("이순신"), new Integer(20), new Double(10.5));
}
}
class Util{
//만능메소드
public static <A,B> Box<A,B> boxig(A a, B b){ //정적메소드
// public static <Integer, Double> Box<Integer,Double> boxig(){ //정적메소드
//메소드에는 리턴타입 양옆으로 써줌 사실 오른쪽 <>안은 생략가능.
//Setter로 값 넣기
Box<A,B> box1 = new Box<A,B>();
box1.setA(a);
box1.setB(b);
System.out.println(box1.getA()+ " " + box1.getB());
return box1;
}
public <A,B,C> Car<A,B,C> running(A a, B b, C c) { //인스턴스메소드
Car<A,B,C> car1 = new Car<A,B,C>();
car1.setA(a);
car1.setB(b);
car1.setC(c);
System.out.println(car1.getA() + " " + car1.getB() + " " + car1.getC());
return car1;
}
}
class Box<A,B>{
A a;
B b;
public Box() { }
public A getA() { return a; }
public void setA(A a) { this.a = a; }
public B getB() {return b;}
public void setB(B b) { this.b = b; }
}
class Car<A,B,C>{
A a;
B b;
C c;
public Car(){ }
public A getA() { return a; }
public void setA(A a) { this.a = a; }
public B getB() { return b; }
public void setB(B b) { this.b = b; }
public C getC() { return c; }
public void setC(C c) { this.c = c; }
}
public class CompareMethodEx {
public static void main(String[] args) {
Pair p1 = new Pair<Integer, String>(1, "사과");
Pair p2 = new Pair<Integer, String>(1, "사과");
boolean result1 = Util.<Integer,String>compare(p1, p2); //구체적인 타입 명시적으로 지정
if(result1) System.out.println("논리적으로 동등한 객체");
else System.out.println("논리적으로 동등하지 않은 객체");
Pair p3 = new Pair<String, String>("user1", "홍길동");
Pair p4 = new Pair<String, String>("user2", "홍길동");
boolean result2 = Util.compare(p3, p4); // 구체적인 타입을 추정 (p3,p4객체 생성시에 이미 타입이 지정되었기 때문에 추정 가능)
if (result2) System.out.println("논리적으로 동등한 객체");
else System.out.println("논리적으로 동등하지 않은 객체");
}
}
class Util{
//제너릭 메소드 : 타입파라미터 리턴타입 메소드명(매개변수)
//해당 메소드는 매개값으로 두 개의 Pair 객체를 받아 두 객체 안의 필드값을 비교하고, 비교 결과를 리턴
public static<K, V> boolean compare(Pair<K,V> p1, Pair<K,V> p2) {
boolean keyCompare = p1.getKey().equals(p2.getKey());
boolean valueCompare = p1.getValue().equals(p2.getValue());
return keyCompare && valueCompare;
}
}
class Pair<K,V>{
private K key;
private V value;
Pair(K key, V value){ this.key = key; this.value = value;}
void setKey(K key) { this.key = key; }
void setValue(V value) { this.value = value; }
K getKey() { return key; }
V getValue() { return value; }
}

제한된 타입 파라미터(<T extends 최상위타입>)
타입 파라미터에 지정되는 구체적인 타입을 제한할 필요가 있을 경우 사용
예를 들어, 숫자를 연산하는 제네릭 메소드는 매개값으로 Number 혹은 그 하위 클래스(Integer, Short, Byte, Long, Double)의 인스턴스만 가져야 한다. 이 때 제한된 타입 파라미터를 사용한다.
타입 파라미터 뒤에 extends 키워드를 붙이고, 상위타입을 명시하여 사용한다.
public <T extends 상위타입(클래스 혹은 인터페이스) > 리턴타입 메소드(매개변수, ...) { ... }
cf. 상위타입은 인터페이스도 가능한데, implements를 쓰지않고 마찬가지로 extends를 사용한다.
static <T extends Number> int compare(T t1, T t2) {
double v1 = t1.doubleValue(); double v2 = t2.doubleValue(); return Double.compare(v1,v2); }
실제로 사용할 때 타입 파라미터에는 상위타입이거나, 상위 타입의 하위 클래스 혹은 구현 클래스만 지정할 수 있다.
int result = Util.<Integer> compare(10,20);
타입 파라미터는 생략 가능하다.
int result = Util. compare(10,20); //단, 매개변수 값이 제한된 타입 파라미터를 따라야 함. 즉 여기선 숫자 외에 다른 형이 올 수 없음
예시
public class BoundedTypeParameterEx {
public static void main(String[] args) {
// String str = Util2.compare("a","b");
//Number도 아니고 , 하위클래스(Byte, Short, Integer, Long, Double)가 아니라 불가
int result1 = Util2.<Integer>compare(10, 20);
System.out.println(result1);
int result2 = Util2.compare(4.5, 3);
System.out.println(result2);
}
}
class Util2 {
static <T extends Number> int compare(T t1, T t2) { //Number이거나 하위 또는 (인터페이스의) 구현 클래스만 가능
double v1 = t1.doubleValue(); //Number의 doubleValue()메소드 사용
double v2 = t2.doubleValue();
return Double.compare(v1, v2); //Double.compare() v1<v2면 -1, 같으면 0, v1>v2면 1을 리턴한다.
}
}

와일드카드 타입 <?> <? extends ...> <? super ...>
코드에서 일반적으로 ?를 와일드카드라고 부른다.
제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드카드를 다음과 같이 세 형태로 사용한다.
제네릭타입<?> : Unbounded WildeCards (제한 없음)
- 모든 클래스나 인터페이스 타입 올 수 있음
제네릭타입<? extends A> : Upper Bounded Wildcards (상위클래스 제한)
- A클래스 혹은 A클래스의 자식 클래스만 올 수 있음
<? super A> : Lower Bounded Wildcards (하위 클래스 제한)
- A 클래스 혹은 A클래스의 상위 클래스만 올 수 있음
타입 파라미터로 배열을 생성하려면 new T[n] 형태로 배열을 생성할 수 없고
(T[ ]) (new Object[n]) 으로 생성해야 한다.
구체적 타입이 정해져 있지 않은 상황에서 Object로 배열을 생성해 모든 데이터의 저장을 가능하게 한 뒤, 다시 T[ ] 제네릭 타입으로 형변환
new T[n]를 했다면 나왔을 것 같은 배열을 얻을 수 있음.
예시
Arrays.toString(배열) : 배열의 값들을 하나하나 꺼내 나열 [a, b, c, ...]의 문자열 형태로 보여준다.
1. 제네릭타입의 Course<T>객체
- 수강 과정의 이름과 수강 정원 만큼의 제네릭 타입 배열(students)을 객체 생성시 저장한다.
- add(T t) method : 메소드가 호출되면 수강인원만큼 반복문을 수행하는데, students배열이 비어있을 때 매개값으로 받은 t를 비어있는 student[i] 저장한다.
- getName, getStudents
2. 그 외 Person, Worker, Student, HighStudent 클래스를 선언
- 각각의 상속관계 설정
- 객체 생성시 수강생의 상태(일반인, 직장인, 학생, 고등학생)를 name에 저장한 뒤 name을 리턴하도록 toString()를 오버라이드
3. 3가지 정적 메소드
- 수업의 이름과 수강생의 목록을 리턴하는 세가지 메소드
① registerCourse(Course<?> course) : 모든 수업을 매개값으로 줄 수 있음
② registerCourseStudent(Course< ? extends Student> course) : Student, HighStudent 타입의 매개값 가짐
③ registerCourseWorker(Course< ? super Worker > course) : Worker, Person 타입의 매개값 가짐
4. 각 과정 객체 생성
- 제네릭타입과 일치하거나 하위타입들의 객체를 add(T t) 메소드의 매개값으로 대입 가능
① 일반인과정 : Course<Person> personCourse = new Course<Person>("일반인과정", 5); →add()의 매개값으로 Person, Worker, Student, HighStudent객체 대입 가능
② 직장인 과정 : Course<Worker> workerCourse = new Course<Worker>("직장인과정", 5); → Worker 객체 대입 가능
③ 학생과정 : Course<Student> studentCourse = new Course<Student>("학생과정", 5); → Student, HighStudent 대입 가능
④ 고등학생 과정 : Course<HighStudent> highStudentCourse = new Course<HighStudent>("고등학생과정", 5); → HighStudent 대입 가능
5. registerCourseXXX 호출
- 4에서 만든 변수를 매개값으로 가지는 registerCourseXXX 메소드 호출
- 각 수강과정의 이름과 수강생의 이름 목록을 확인할 수 있음
public class WildCardEx {
//Course를 등록하는 메소드로, 모든 클래스와 인터페이스 타입이 올 수 있음.
//Arrays.toString(배열)은 배열의 값들을 [a, b, c, ... ]로 나열해서 문자열로 만드는 메소드
public static void registerCourse(Course<?> course) {
System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents()));}
//Student이나 그 하위타입(HighStudent) 올 수 있음
public static void registerCourseStudent(Course<? extends Student> course) {
System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents()));}
//Worker이나 상위타입(Person) 올 수 있음
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);//new Course<Person>("일반인 과정", 5);
//students[] 의 구체적인 타입은 생성자 호출 과정에서 정해졌다 >> Person >> Person은 다른 클래스들의 상위타입이기 때문에 자동형변환되어 저장됨.
personCourse.add(new Person("일반인")); //0열 일반인
personCourse.add(new Worker("직장인")); //1열 직장인
personCourse.add(new Student("학생")); //2열 학생
personCourse.add(new HighStudent("고등학생")); //3열 고등학생
Course<Worker> workerCourse = new Course<Worker>("직장인과정", 5);
//Worker 혹은 하위타입은 Worker뿐이다. 따라서 다른 객체 저장 불가.
// workerCourse.add(new Person("일반인")); workerCourse.add(new Student("학생")); workerCourse.add(new HighStudent("고등학생"));
workerCourse.add(new Worker("직장인"));
Course<Student> studentCourse = new Course<Student>("학생과정", 5);
//Student형에 저장 가능한 객체는 Student, highStudent
// studentCourse.add(new Person("일반인")); studentCourse.add(new Worker("직장인"));
studentCourse.add(new Student("학생"));
studentCourse.add(new HighStudent("고등학생"));
Course<HighStudent> highStudentCourse = new Course<HighStudent>("고등학생과정", 5);
//오직 HighStudent객체만 저장 가능
// highStudentCourse.add(new Person("일반인")); highStudentCourse.add(new Worker("직장인")); highStudentCourse.add(new Student("학생"));
highStudentCourse.add(new HighStudent("고등학생"));
registerCourse(personCourse);
registerCourse(workerCourse);
registerCourse(studentCourse);
registerCourse(highStudentCourse);
System.out.println();
// registerCourseStudent(personCourse);
// registerCourseStudent(workerCourse);
registerCourseStudent(studentCourse);
registerCourseStudent(highStudentCourse);
System.out.println();
registerCourseWorker(personCourse);
registerCourseWorker(workerCourse);
// registerCourseWorker(studentCourse);
// registerCourseWorker(highStudentCourse);
}
}
//Course 제네릭타입으로 선언
class Course<T>{
private String name;
private T[] students;
Course(String name, int capacity) {
this.name = name;
students = (T[]) (new Object[capacity]); //수강인원 수만큼의 Object배열을 만들어 후에 구체적 타입이 정해지면 강제형변환하여 저장
}
String getName() { return name; }
T[] getStudents() { return students;}
void add(T t) {
for(int i=0; i<students.length; i++) { //수강인원만큼 반복문 수행
if(students[i] == null) { //students배열의 해당 열이 비어있으면 매개값 t를 넣고 반복 종료
students[i] = t;
break;
}
}
}
}
class Person {
String name;
Person(String status) { this.name = status;}
Person(){}
@Override
public String toString() {
return name;
}
}
class Worker extends Person{
String name;
Worker(String status){ this.name = status; }
@Override
public String toString() {
return name;
}
}
class Student extends Person{
String name;
Student(String status){ this.name = status; }
Student(){}
@Override
public String toString() {
return name;
}
}
class HighStudent extends Student{
String name;
HighStudent(String status) { this.name = status; }
@Override
public String toString() {
return name;
}
}

제네릭 타입의 상속과 구현
클래스가 상속관계를 가질 때 혹은 인터페이스를 구현할 때 제네릭 타입 또한 상속된다.
단, 자식 제네릭 타입은 추가적인 타입 파라미터를 가질 수 있다
class ChildProduct<T, M, C> extends Product<T, M> { ... }
class StorageImpl <T> implements Storage<T> { ... }
예시
public class ChildProductAndStorageEx {
public static void main(String[] args) {
ChildProduct<Tv, String, String> product = new ChildProduct<>();
product.setKind(new Tv()); //부모의 메소드
product.setModel("SmartTV"); //부모의 메소드
product.setCompany("Samsumg"); //자신의 메소드
Storage<Tv> storage = new StorageImpl<Tv>(100); //capacity가 100, 제네릭타입이 TV인 구현클래스 만듦.
storage.add(new Tv(), 0); //0열엔 Tv()객체넣기
Tv tv = storage.get(0);
}
}
class Product <T, M>{//부모클래스
private T kind;
private M model;
T getKind() { return this.kind;}
M getModel() { return this.model;}
void setKind(T kind) { this.kind = kind;}
void setModel(M model) { this.model = model; }
}
class Tv{ }
class ChildProduct <T,M,C> extends Product<T,M>{ //자식클래스 - 부모의 제네릭 타입 함께 써줌
private C company;
C getCompany() { return this.company; }
void setCompany ( C company ) { this.company = company; }
}
interface Storage<T> { //인터페이스
void add(T item, int index);
T get(int index);
}
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]; }
}
Ch13 확인문제
(마지막 문제 - 제네릭 타입의 상속)
Util클래스 (공유객체) 자체가 제네릭 타입을 가지지 않는다 할지라도 Util클래스의 정적메소드는 제네릭 타입을 가질 수 있다.
제네릭 메소드의 파라미터 타입(T) 중 하나가 다른 제네릭 타입(A,B)을 가지는 클래스(Pair)를 상속할 때 이 메소드는 T,A,B를 파라미터 타입으로 가져야 한다.
static <T extends Pair<A,B>, A, B, U> B get(T t, U u){ return ... } cf. T는 Pair이거나 자손클래스
확인문제 Q2,3,4
package check;
public class ContainerExample {
public static void main(String[] args) {
Container<String> container1 = new Container<String>();
container1.set("홍길동");
String str = container1.get();
System.out.println(str);
Container<Integer> container2 = new Container<Integer>();
container2.set(6);
int value = container2.get();
System.out.println(value);
}
}
class Container<T>{
T t;
Container(){
}
Container(T t){
this.t = t;
}
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
}
package check; public class ContainerExample2 { public static void main(String[] args) { Container2<String, String> container1 = new Container2<String, String>(); container1.set("홍길동","도적"); String name1 = container1.getKey(); String job = container1.getValue(); Container2<String, Integer> container2 = new Container2<String, Integer>(); container2.set("홍길동", 35); String name2 = container2.getKey(); int age = container2.getValue(); } } class Container2<K, V>{ K key; V value; Container2(){ } Container2(K key, V value){ this.value = value; this.key = key; } void set(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } }
package check.myself;
public class UtilEx {
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(childPair, "홍삼순");
System.out.println(childAge);
// OtherPair<String, Integer> otherPair = new OtherPair<>("홍삼원", 20);
// OtherPair는 Pair를 상속하지 않으므로 예외가 발생한다. T extends Pair<K, V>를 만족X
// int otherAge = Util.getValue(otherPair, "홍삼원");
// System.out.println(otherAge);
}
}
class Pair<K,V>{
private K key;
private V value;
Pair(K key, V value){ this.key = key; this.value = value;}
K getKey() { return key; }
V getValue() { return value; }
}
class ChildPair<K,V> extends Pair<K,V>{
ChildPair(K k, V v) {super(k,v);}
}
class OtherPair<K,V>{
private K key;
private V value;
OtherPair(K key, V value){ this.key = key; this.value = value;}
K getKey() { return key; }
V getValue() { return value; }
}
class Util{
//여기서 T는 파라미터로 받는 t의 타입으로, 최소 Pair<K,V>를 상속하고 있는 객체(Pair<K,V> 혹은 하위타입만을 t로 받을 수 있다)이다.==> ①T extends Pair<K,V>
//Util의 타입 파라미터 T가 Pair<K,V>를 상속하므로, 해당 메소드는 K,V또한 타입파라미터로 가져야한다. ②<K,V>
//제네릭 타입 U u를 매개값으로 가지기 때문에 마지막으로 U를 타입파라미터로 가진다. ③<U>
//즉, < ①T extends Pair<K,V> ②K,V ③U >가 getValue메소드가 가지는 타입파라미터가 된다.
//그리고 getValue의 리턴타입은 V
static <T extends Pair<K,V>,K,V, U> V getValue(T t, U u ) {
if(t.getKey()==u) return t.getValue();
else return null;
}
}

'Java > Study' 카테고리의 다른 글
Ch16 스트림과 병렬처리 (0) | 2021.04.09 |
---|---|
Ch15 확인문제 (0) | 2021.04.02 |
Ch15 컬렉션 프레임워크 (0) | 2021.03.31 |
Ch12. 멀티스레드 (0) | 2021.03.29 |
Ch11 확인문제 (0) | 2021.03.29 |