본문 바로가기

Java/Study

Ch13 제네릭

제네릭

컴파일 시 강한 타입 체크 - 컴파일러가 에러를 사전에 방지하게 한다.

타입변환을 제거한다 - 애초에 담아올 타입을 국한함으로써 불필요한 타입 변환을 방지한다.

 

 

제네릭 타입( 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