제한된 지네릭 클래스

 

- extends로 대입할 수 있는 타입을 제한

class FruitBox<T extends Fruit> {	// Fruit과 그 자손만 타입으로 지정가능
	ArrayList<T> list = new ArrayList<T>();
	...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러. Toy는 Fruit의 자손이 아님

 

- 인터페이스인 경우에도 extends를 사용

interface Eatable {}
class FruitBox<T extends Eatable> {...}

 

 

ex)

import java.util.ArrayList;

interface Eatable {}

class Fruit implements Eatable {
	public String toString() {return "Fruit";}
}
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";}
}

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(); }
}
public 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>();	// 에러
		Box<Toy>	toyBox2 = new Box<Toy>();	// OK
		
		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);
		

	}

}

 

 

 

⦁ 지네릭스의 제약

- 타입 변수에 대입은 인스턴스마다 다르게 가능

Box<Apple> appleBox = new Box<Apple>();	// OK. Apple 객체만 저장가능
Box<Grape> grapeBox = new Box<Grape>();	// OK. Grape 객체만 저장가능

 

- static멤버에 타입 변수 사용 불가

(static 멤버는 모든 인스턴스에 공통인 부분이므로)

class Box<T> {
	static T item; // 에러
	static int compare(T t1, T t2) { ... } // 에러
	...
}

 

- 배열 생성할 때 타입 변수 사용불가. 타입 변수로 배열 선언은 가능

(new 연산자 뒤에 오는 타입은 확정적이어야 함. 그러므로 객체나 배열 생성시 사용 불가능)

class Box<T> {
	T[] itemArr;	// 선언은 OK. T타입의 배열을 위한 참조변수
	...
	T[] toArray() {
		T[] tmpArr = new T[itemArr.length]; // 에러. 지네릭 배열 생성불가
		...

 

 

 

와일드 카드 <?>

- 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능

ArrayList<? extends Product> list = new ArrayList<Tv>();	// OK
ArrayList<? extends Product> list = new ArrayList<Audio>();	// OK
ArrayList<Product> list = new ArrayList<Tv>(); // 에러, 대입된 타입 불일치

 

<? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능

<? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능

<?> 제한 없음. 모든 타입이 가능. <? extends Object>와 동일

 

- 메소드의 매개변수에 와일드 카드를 사용

static Juice makeJuce(FruixBox<? extends Fruit> box) {
	String tmp = "";
	for(Fruit f : box.getList()) 
		tmp += f+ " ";
	return new Juice(tmp);
}

 

ex)

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 {
	static Juice makeJuice(FruitBox2<? extends Fruit2> box) {
		String tmp = "";
		
		for(Fruit2 f : box.getList())
			tmp += f + " ";
		return new Juice(tmp);
		
	}
}

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();}
	
}
public class Ex12_4 {

	public static void main(String[] args) {
		FruitBox2<Fruit2> fruitBox = new FruitBox2<Fruit2>();
		FruitBox2<Apple2> appleBox = new FruitBox2<Apple2>();
//		FruitBox2<? extends Fruit2> 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));
		
		
		
	}

}

 

 

 

⦁ 지네릭 메소드

- 지네릭 타입이 선언된 메소드(타입 변수는 메소드 내에서만 유효) (지역 변수처럼)

static <T> void sort(List<T> list, Comparator<? super T> c)

 

- 클래스의 타입 매개변수<T>와 메소드의 타입 매개변수 <T>는 별개이다.

class FruitBox<T> {
	...
	static <T> void sort(List<T> list, Comparator<? super T> c) {
	...
	}
}

(FruitBox의 타입 매개변수와 sort메소드의 타입 변수는 별개)

 

- 메소드를 호출할 때마다 타입을 대입해야함(대부분 생략 가능)

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
	...
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));

 

왜냐하면

static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
	String tmp = "";
	for (Fruit f : box.getList()) tmp += f + " ";
	return new Juice(tmp);
}

makeJuice 메소드가 받는 매개변수의 타입변수가 메소드에서 사용하는 타입변수이기 때문에 생략이 가능.

 

 

- 메소드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가

System.out.println(<Fruit>makeJuice(fruitBox));		// 에러, 클래스 이름 생략불가
System.out.println(this.<Fruit>makeJuice(fruitBox));	// OK
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));	// OK

 

 

⦁ 지네릭 타입의 형변환

- 지네릭 타입과 원시 타입간의 형변환은 바람직하지 않다.(경고 발생)

Box<Object> objBox = null;
Box box = (Box)objBox; // OK. 제네릭 타입->원시타입. 경고 발생
objBox = (Box<Object>)box; // OK. 원시타입->제네릭 타입. 경고 발생
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>)strBox; // 에러, Box<String> -> Box<Object> 
strBox = (Box<String>)objBox; // 에러, Box<Object> -> Box<String>

 

- 와일드카드가 사용된 지네릭 타입으로는 형변환 가능

Box<Object> objBox = (Box<Object>)new Box<String>(); // 에러, 형변환 불가능
Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>(); // OK
Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일

 

 

⦁ 지네릭 타입의 제거

- 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.

1. 지네릭 타입의 경계(bound)를 제거

2. 지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가

3. 와일드 카드가 포함된 경우, 적절한 타임으로 형변환 추가

 

 

열거형(enum)

- 관련된 상수들을 같이 묶어 놓은 것. Java는 타입에 안전한 열거형을 제공

class Card {
	static final int ClOVER = 0;
	static final int HEART = 1;
	static final int DIAMOND = 2;
	static final int SPADE = 3;

	static final int TWO = 0;
	static final int THREE = 1;
	static final int FOUR = 2;

	final int kind;
	final int num;
}

이것을 간단하게 정의할 수 있다.

class Card {
		  //   0	1	 2	  3
	enum Kind {Clover, HEART, DIAMOND, SPADE} // 열거형 Kind를 정의
	enum Value {TWO, THREE, FOUR} // 열거형 Value를 정의
		 //	0	1	2
	
	final Kind kind;		// 타입이 int가 아닌 Kind임에 유의하자.
	final Value value;
}

자동으로 값이 부여가 됨.

Java의 열거형은 값&타입을 모두 체크함.

 
 

열거형의 정의와 사용

- 열거형을 정의하는 방법

enum 열거형이름 { 상수명1, 상수명2, ...}

 

- 열거형 타입의 변수를 선언하고 사용하는 방법

enum Direction { EAST, SOUTH, WEST, NORTH }

class Unit {
	int x, y;	// 유닛의 위치
	Direction dir;	// 열거형 인스턴스 변수를 선언
	
	void init() {
		dir = Direction.EAST; // 유닛의 방향을 EAST로 초기화
	}
}

 

- 열거형 상수의 비교에 ==compareTo() 사용가능

if(dir==Direction.EAST) {
	x++;
} else if (dir>Direction.WEST) { // 에러, 열거형 상수에 비교연산자 사용불가
	...
} else if (dir.compareTo(Direction.WEST) > 0) { // compareTo 사용 가능

 

열거형의 조상 java.lang.Enum

- 모든 열거형은 Enum의 자손이며, 아래의 메소드를 상속받는다.

 

- values(), valueOf()는 컴파일러가 자동으로 추가

static E[] values()

static E valueOf(String name)

 
Direction[] dArr = Direction.values();

for(Direction d : dArr)
	System.out.printf(“%s=%d%n”, d.name(), d.ordinal());

 

 

ex)

//	 	0	1	2	3
enum Direction { EAST, SOUTH, WEST, NORTH }

public class Ex12_5 {

	public static void main(String[] args) {
		Direction d1 = Direction.EAST; // 열거형타입.상수이름
		Direction d2 = Direction.valueOf("WEST");
		Direction d3 = Enum.valueOf(Direction.class, "EAST");
		
		System.out.println("d1="+d1);
		System.out.println("d2="+d2);
		System.out.println("d3="+d3);
		
		System.out.println("d1==d2 ? "+ (d1==d2)); // false
		System.out.println("d1==d3 ? "+ (d1==d3)); // true
		System.out.println("d1.equals(d3) ? "+ d1.equals(d3));
//		System.out.println("d2 > d3 ?"+ (d1 > d3)); // 에러
		System.out.println("d1.compareTo(d3) ? " + (d1.compareTo(d3)));
		System.out.println("d1.compareTo(d2) ? " + (d1.compareTo(d2)));
		
		switch(d1) {
			case EAST : // Direction.EAST라고 쓸 수 없다.
				System.out.println("The direction is EAST."); break;
			case SOUTH :
				System.out.println("The direction is SOUTH."); break;
			case WEST :
				System.out.println("The direction is WEST."); break;
			case NORTH :
				System.out.println("The direction is NORTH."); break;
			default:
				System.out.println("Invalid direction."); break;
		}
		
		Direction[] dArr = Direction.values(); // 열거형의 모든 상수를 배열로 반환
		
		for(Direction d : dArr) // for(Direction d : Direction.values())
			System.out.printf("%s=%d%n", d.name(), d.ordinal()); // 정의된 순서
	}

}

출처 - 유튜브 남궁성의 정석코딩 [자바의 정석 - 기초편] 

+ Recent posts