관리 메뉴

나구리의 개발공부기록

자바의 정석 기초편 ch12 - 7 ~ 14[제네릭스 적용 예제(Iterator, HashMap), 제한된 제네릭클래스, 제네릭스의 제약, 와일드카드, 제네릭메서드] 본문

유튜브 공부/JAVA의 정석 기초편(유튜브)

자바의 정석 기초편 ch12 - 7 ~ 14[제네릭스 적용 예제(Iterator, HashMap), 제한된 제네릭클래스, 제네릭스의 제약, 와일드카드, 제네릭메서드]

소소한나구리 2023. 12. 13. 15:13

1) 제네릭스가 적용된 Iterator<E> 

  • 기존의 Iterator 인터페이스가 정의 되었을 때는 Object클래스가 직접 쓰였지만 이후에는 제네릭이 적용되어 타입변수로 메서드를 정의하고 있음
  • Iterator를 사용 시 제네릭스를 적용하면 형변환 없이 지정한 타입의 참조변수에 참조값을 저장할 수 있음

좌 / 기존의 Iterator, 우 / 제네릭스 타입변수가 사용 된 Iterator

 

제네릭스 적용으로 메서드 호출 시 형변환을 생략할 수 있음

(1) 예제

  • ArrayList와 Iterator에 제네릭스를 적용하여 형변환 없이 값을 출력하는 예제
import java.util.*;

class Ex12_2 {
	public static void main(String[] args) {
		ArrayList<Student> list = new ArrayList<Student>();
		list.add(new Student("자바왕", 1, 1));
		list.add(new Student("자바짱", 1, 2));
		list.add(new Student("홍길동", 2, 1));

		Iterator<Student> it = list.iterator();	//<Student>제네릭스 사용
		while (it.hasNext()) {
		//  Student s = (Student)it.next(); // 제네릭스를 사용하지 않으면 형변환 필요.
//			Student s = it.next();
//			System.out.println(s.name);
			System.out.println(it.next().name);	// 위2줄을 한줄로
		}
	} // main
}

class Student {
	String name = "";
	int ban;
	int no;

	Student(String name, int ban, int no) {
		this.name = name;
		this.ban = ban;
		this.no = no;
	}
}

2) 제네릭스가 적용된 HashMap<K, V>

  • 여러개의 타입 변수가 필요한 경우 ,(콤마)를 구분자로 선언함
  • HashMap의 내부 코드를 보면 타입 변수를 적용했음에도 get과 remove의 매개변수 타입이 계속 Object인 이유는 내부 메서드의 hash(key)가 Object로 되어있기 때문에 불필요한 형변환은 하지 않았기 때문임

 

(1) 예제

  • JDK1.7부터 객체 생성시 생성자에 입력하는 제네릭타입 지정은 생략이 가능함
  • HashMap의 타입을 key에는 String, value에는 Student2로 지정한 예제
import java.util.*;

class Ex12_2_1 {
	public static void main(String[] args) {
		// JDK1.7부터 생성자의 타입지정은 생략 가능
		HashMap<String, Student2> map = new HashMap< >();
		
		map.put("자바왕", new Student2("자바왕", 1, 1, 100, 100, 100));
		
		// public Student2 get(Object key) {
		// Student2 s = map.get("자바왕");	// map.get에(Student2)형변환 생략 가능
		// System.out.println(s.name);
        
		System.out.println(map.get("자바왕").name); //위 두코드를 한번에	
	} // main
}

class Student2 {
	String name = "";
	int ban;
	int no;
	int kor;
	int eng;
	int math;

	Student2(String name, int ban, int no, int kor, int eng, int math) {
		this.name = name;
		this.ban = ban;
		this.no = no;
		this.kor = kor;
		this.eng = eng;
		this.math = math;
	}
}

3) 제한된 제네릭 클래스

  • 타입변수에 extends 키워드로 대입할 수 있는 타입을 제한할 수 있음
  • 지정한 타입과 그 자손 타입만 대입할 수 있도록 범위를 제한할 수 있음
  • 제네릭 타입변수에 범위를 지정할 때는 인터페이스인 경우에도 extends를 사용함
  • class FruitBox<T extends Fruit & Eatable> 처럼 제네릭 타입변수의 범위를 지정
  • FruitBox의 클래스에는 Fruit클래스와 Eatable인터페이스를 구현한 클래스의 자손만 들어올 수 있음
import java.util.ArrayList;

// 최고조상 Fruit클래스, Eatable 인터페이스를 구현
class Fruit implements Eatable {
	public String toString() { return "Fruit";}
}

// Fruit을 상속받은 Apple, Grape 클래스, Toy는 아무것도 아님
class Apple extends Fruit { public String toString() { return "Apple";}}
class Grape extends Fruit { public String toString() { return "Grape";}}
class Toy                 { public String toString() { return "Toy"  ;}}

interface Eatable {}

class Ex12_3 {
	public static void main(String[] args) {
		FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
		FruitBox<Apple> appleBox = new FruitBox<Apple>();
		FruitBox<Grape> grapeBox = new FruitBox<Grape>();
//		FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // 에러. 타입 불일치
//		FruitBox<Toy>   toyBox   = new FruitBox<Toy>();   // 에러. Toy는 Fruit의 자손이 아님

		fruitBox.add(new Fruit());
		fruitBox.add(new Apple());
		fruitBox.add(new Grape());
		appleBox.add(new Apple());
//		appleBox.add(new Grape());  // 에러. Grape는 Apple의 자손이 아님
		grapeBox.add(new Grape());

		System.out.println("fruitBox-"+fruitBox);
		System.out.println("appleBox-"+appleBox);
		System.out.println("grapeBox-"+grapeBox);
	}  // main
}

// FruitBox의 클래스에는 Fruit클래스와 Eatable인터페이스를 구현한 클래스의 자손만 들어올 수 있음
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}

class Box<T> {
	ArrayList<T> list = new ArrayList<T>();	    // item을 저장할 list
	void add(T item) { list.add(item);     }    // 박스에 item을 저장
	T get(int i)     { return list.get(i); }    // 박스에서 item을 꺼냄
	int size()       { return list.size(); }
	public String toString() { return list.toString();}
}

 

4) 제네릭스의 제약

(1) 타입 변수에는 인스턴스 별로 타입을 다르게 대입할 수 있음

  • static멤버는 모든 인스턴스에 공통으로 사용하는 멤버이기 때문에 타입 변수 사용 불가
  • 배열을 생성할 때 타입 변수로 생성할 수 없고 참조변수의 선언은 가능함
  • new 다음에는 확정된 타입이 와야 하는데 타입변수는 무슨 타입이 올지 미확정이기 때문
class Box<T> {
	static T item;	// static 변수에 타입변수를 적용하여 에러발생
	static int compare(T t1, t t2) {...}	// static 메서드의 매개변수에 타입변수를 적용하여 에러발생
    
	T[] itemArr;	// OK - 타입변수 배열인 참조변수 선언은 가능함
    
	T[] toArray(){
 		T[] tmpArr = new T[itemArr.length];	//에러, new연산자의 뒤에 타입변수를 사용할 수 없음   
    }
}

 

5) 와일드카드 < ? >

  • 하나의 참조변수로 서로 다른 타입이 대입된 여러 제네릭 객체를 다루기 위함(다형성의 효과를 얻을 수 있음)
  • 메서드의 매개변수에도 와일드 카드를 사용 가능함

(1) 와일드카드 종류

  • <? extends T> : T와 그 자손들만 사용 가능 (와일드 카드의 상한 제한) - 주로 사용
  • <? super T> : T와 그 조상들만 사용 가능 (와일드 카드의 하한 제한)
  • < ? > : 제한 없음. 모든 타입이 가능 <? extends Object>와 동일
// 와일드 카드 사용으로 제네릭타입이 다르지만 사용 가능함(Product과 그의 자손)
ArrayList<? extends Product> list = new ArrayList<Tv>();
ArrayList<? extends Product> list = new ArrayList<Audio>();

// 메서드의 매개변수에도 와일드 카드 사용 가능함
static Juice makeJuice(FruitBox<? extends Fruit> box) {
	...
    }
    
System.out.println(Juice.makeJuice(new FruitBox<Fruit>()));	// OK
System.out.println(Juice.makeJuice(new FruitBox<Apple>()));	// OK

 

(2) 예제

import java.util.ArrayList;

class Fruit2		       	{ public String toString() { return "Fruit";}}
class Apple2 extends Fruit2	{ public String toString() { return "Apple";}}
class Grape2 extends Fruit2	{ public String toString() { return "Grape";}}

class Juice {
	String name;

	Juice(String name)       { this.name = name + "Juice"; }
	public String toString() { return name;                }
}

class Juicer {
	// Fruit2와 그 자손을 사용할 수 있도록 와일드카드 사용
	static Juice makeJuice(FruitBox2<? extends Fruit2> box) {
		String tmp = "";

		// 향상된 for문
		for(Fruit2 f : box.getList()) 
			tmp += f + " ";
		return new Juice(tmp);
	}
}

class Ex12_4 {
	public static void main(String[] args) {
		FruitBox2<Fruit2> fruitBox = new FruitBox2<Fruit2>();
		FruitBox2<Apple2> appleBox = new FruitBox2<Apple2>();

		fruitBox.add(new Apple2());
		fruitBox.add(new Grape2());
		appleBox.add(new Apple2());
		appleBox.add(new Apple2());

		System.out.println(Juicer.makeJuice(fruitBox));
		System.out.println(Juicer.makeJuice(appleBox));
	}  // main
}

class FruitBox2<T extends Fruit2> extends Box2<T> {}

class Box2<T> {
	ArrayList<T> list = new ArrayList<T>();
	void add(T item) { list.add(item);      }
	T get(int i)     { return list.get(i);  }
	ArrayList<T> getList() { return list;   }
	int size()       { return list.size();  }
	public String toString() { return list.toString();}
}

6) 제네릭메서드

  • 메서드에 타입 변수가 선언 된 것, 즉 메서드를 호출할 때마다 메서드의 타입을 지정할 수 있음
  • 타입 변수는 메서드 내에서만 유효함
  • 클래스의 타입 변수<T>와 메서드의 타입 변수<T>는 별개임
class FruitBox <T> {	// 제네릭클래스 <T> 타입 선언	
	...
	// 제네릭 메서드에 있는 타입변수 <T> - 제네릭메서드 안에서만 적용되는 타입변수
	// 문자가 일치하여도 클래스와 메서드의 <T>는 각각 다른 타입 변수임(별개임)
	static <T> void sort(List<T> list, Comparator<? super T> c) {
	...
    }
}

 

  • 제네릭 메서드를 사용하려면 메서드를 호출할 때마다 타입을 대입해야 함
  • 그러나 대부분은 제네릭 메서드의 타입과, 제네릭 메서드를 호출한 참조변수의 타입이 동일하기 때문에 생략이 가능함
// 메서드를 호출할 때마다 타입을 대입
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
...

// 메서드 호출시 대부분 타입 생략 가능
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));	// <Fruit>생략 가능	
System.out.println(Juicer.<Apple>makeJuice(appleBox));	// <Apple>생략 가능	


// 제네릭 메서드
static<T extends Fruit> Juice makeJuice(FruitBox<T> box) {
	String tmp = "";
	for(Fruit f : box.getList()) tmp += f + "";
	return new Juice(tmp);
}

 

  • 타입 생략이 불가능하여 타입을 작성해야한다면 클래스이름이나 this를 꼭 사용해야함
  • 타입 생략이 불가능한 경우가 드물게 발생함
// 메서드 호출 시 타입을 생략하지 않을 때는 클래스 이름 생략 불가
System.out.println(<Fruit>makeJuice(fruitBox));	// 클래스이름 생략 -> 에러

// this.<Fruit> or 클래스이름.<Fruit> 둘중 하나처럼 작성해야 함
System.out.println(this.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));

 

(1) 와일드카드와 제네릭메서드의 목적

  • 서로의 목적은 다르지만 와일드카드가 쓸 수 없을때 제네릭메서드를 사용하는 경우가 많음
  • 제네릭메서드 - 메서드를 호출할 때마다 다른 제네릭 타입을 대입할 수 있게 한 것
  • 와일드카드 - 하나의 참조변수로 서로 다른 타입이 대입된 여러 제네릭스 객체를 다루기 위한 것

 

** 출처 : 남궁성의 정석코딩_자바의정석_기초편 유튜브 강의