[JAVA] Chapter 12 지네릭스, 열거형, 애너테이션 - 1

WOOK JONG KIM·2022년 9월 28일

자바의 정석

목록 보기
16/25
post-thumbnail

ch12-1 지네릭스(Generics)

  • 컴파일 시 타입을 체크해 주는 기능

  • 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어듬
    -> 의도치 않은 타입의 객체가 저장되는 것을 막음

// Tv객체만 저장할 수 있는 ArrayList를 생성
ArrayList<Tv> tvList = new ArrayList<Tv>();

tvList.add(new Tv()); // OK
tvList.add(new Audio()); // 컴파일 에러, TV 외 저장 불가
  • 타입 체크와 형변환을 생략할 수 있어 코드가 간결해 짐

지네릭스 적용 전

ArrayList tvList = new ArrayList();
tvList.add(new Tv());
Tv t = (TV)tvlist.get(0);

적용 후

Arraylist<Tv> tvList = new ArrayList<Tv>();
tvList.add(new TV());
Tv t = tvList.get(0);

ch12-2, 3 타입 변수

클래스 선언 시 붙이는 < E > 안에 E를 타입 변수라 부름

public class ArrayList<E> extends AbstractList<E>
{
	private transient E[] elementData;
    public boolean add(E o) {}
    public E get(int index){}
    ...
}

기존 ArrayList 코드

public class ArrayList extends AbstractList
{
	private transient Object[] elementData;
    public boolean add(Object o){};
    public Object get(int index){};
}

타입변수가 여러개인 경우 < K, V > 와 같이 작성

사용 예시

// 타입 변수 E 대신에 실제 타입 Tv를 대입
ArrayList<Tv> tvList = new ArrayList<Tv>();
public class ArrayList extends AbstractList
{
	private transient Tv[] elementData;
    public boolean add(Tv o){};
    public Tv get(int index){}
}

get이 Object가 아닌 Tv를 반환하기에 형 변환이 필요 없는것!


ch12-4 지네릭스 용어

class Box<T> {}
  • Box< T > 는 T의 박스라고 읽음
  • T는 타입 변수, Box는 원시 타입
  • T에 어떠한 타입을 대입할 시 그 타입은 대입된 타입

Box< Integer > 과 Box< String > 은 별개의 클래스 X
-> 서로 다른 타입을 대입해서 호출 하는 것!

컴파일 후에는 둘 다 원시 타입인 Box로 바뀜


ch12-5,6 지네릭 타입과 다형성

지네릭 클래스 객체 생성 시, 참조변수에 지정해준 지네릭 타입과 생성자에 지정해준 지네릭 타입은 일치해야 함

ArrayList<Tv> list = new ArrayList<Tv>(); //OK
ArrayList<Product> list = new ArrayList<Tv>(); // 에러
class Product(){ }
class Tv extends Product{ }
class audio extends Product{ }

자손 객체 저장

ArrayList<Product> list = newArrayList<Product>();
list.add(new Product());
list.add(new Tv()); //OK
list.add(new Audio()); //OK

/*
꺼낼떄 형변환 필요
Product p - list.get(0);
Tv t = (Tv)list.get(1);
*/

클래스 타입 간 다형성 적용은 가능(지네릭 타입은 일치)

List<Tv> list = new ArrayList<Tv>();
List<Tv> list = new LinkedList<Tv>();
package ch12;

import java.util.*;

class Product{}
class Tv extends Product{}
class Audio extends Product{}

public class Ex12_1 {

	public static void main(String[] args) {
		ArrayList<Product> productList = new ArrayList<Product>();
		ArrayList<Tv> tvList = new ArrayList<Tv>();
		// ArrayList<Product> tvList = new ArrayList<Tv>(); //에러
		// List<Tv> tvList = new ArrayList<Tv>();
		
		productList.add(new Tv());
		productList.add(new Audio());
		
		tvList.add(new Tv());
		tvList.add(new Tv());
		
		printAll(productList);
		//printAll(tvList) // 컴파일 에러 발생 

	}
	
	public static void printAll(ArrayList<Product> list)
	{
		for(Product p: list)
			System.out.println(p);
	}

}
ch12.Tv@ea30797
ch12.Audio@58d25a40

ch12-7 Iterator< E >

Iterator에도 제네릭스 적용 가능

public interface Iterator<E>
{
	boolean hasNext();
    E next();
    void remove();
}
package ch12;

import java.util.*;

public 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();
		while(it.hasNext()) {
			Student s = it.next(); // 제네릭스를 사용하였으므로 형변환 필요 X
			System.out.println(s.name);
		}
	}

}

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

ch 12-8 HashMap< K,V >

HashMap처럼 데이터를 키와 값 형태로 저장하는 컬렉션 클래스는 지정해 줘야 할 타입이 두개 < K, V>

public class HashMap<K,V> extends AbstractMap<K,v>{
	...
    public V get(Object Key){ }
    public V put (K key, V value){ }
    public V remove(Object key) { }
    ...
}

활용 예시

HashMap<String,Student> map = new HashMap<String,Student>();
map.put("자바왕", new Student("자바왕", 1, 1, 100,100,100));

/*
Student s1 = (Student)map.get("1-1");
이 경우 형변환 필요 X
*/

ch 12-9, 10 제한된 지네릭 클래스

타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한하는 방법

예시

// 만약 인터페이스를 구현해야 한다는 제약이 필요할 때도, extends 를 사용
// implements X

class FruitBox<T extends Fruit>
{
	//Fruit의 자손만 타입으로 지정가능
    ArrayList<T> list = new ArrayList<T>();
  	....
}

FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러

// 이런 식으로도 가능
class FruitBox<T extends Fruit & Eatable> {...}
package ch12;

import java.util.ArrayList;

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";}}

interface Eatable {}

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>(); 에러 
		 */
		
		fruitBox.add(new Fruit());
		fruitBox.add(new Apple());
		fruitBox.add(new Grape());
		appleBox.add(new Apple());
// 		appleBox.add(new Grape()); // 에러 Grape는 애플의 자손이 아님
		grapeBox.add(new Grape());
		
		System.out.println("fruitBox-" +fruitBox);
		System.out.println("appleBox-" +appleBox);
		System.out.println("grapeBox-" + grapeBox);		

	}

}

class FruitBox<T extends Fruit & Eatable> extends Box<T>{}

class Box<T>{
	ArrayList<T> list = new ArrayList<T>();
	void add(T item) { list.add(item);}
	T get (int i) { return list.get(i);}
	int size() { return list.size();}
	public String toString() { return list.toString();}
	
}
fruitBox-[Fruit, Apple, Grape]
appleBox-[Apple]
grapeBox-[Grape]

ch12-11 지네릭스 제약

  • 지네릭 클래스 생성 시 객체별로 다른 타입을 지정하는 것이 적절
Box<Apple> appleBox = new Box<Apple>();
Box<Grape> appleBox = new Box<Grape>();
  • T는 인스턴스 변수로 간주되기에 static 멤버타입 변수 T 사용 불가

  • 즉 Box< Apple >.item과 Box< Grape >.item이 다른 것이면 안된다.

class Box<T>
{
	static T item; // 에러
    static int compare(T t1, T t2){ ... } // 에러
}
  • 지네릭 타입의 참조변수를 선언하는 건 가능하지만, 배열을 생성하는 것은 안된다(new)

  • newinstanceof는 컴파일 시점에 타입이 뭔지 정확히 알아야 하는데, 컴파일 하는 시점에서는 T가 어떤 타입이 될지 알 수 없다

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

ch12-12, 13 와일드 카드

지네릭 클래스 생성 시, 참조변수에 지정된 지네릭 타입과 생성자에 지정된 지네릭 타입은 일치해야 한다

// Ok. 지네릭 타입 일치 
ArrayList<Tv> list = new ArrayList<Tv>(); 

// Ok. 다형성. 지네릭 타입 일치
List<Tv> list = new ArrayList<Tv>();

// 에러. 지네릭 타입 불일치
ArrayList<Product> list = new ArrayList<Tv>();

결론적으로 지네릭 타입에 다형성이 적용되있지 않음
-> 이를 구현하려면 와일드 카드 사용

<? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> 와일드 카드 하한 제한. T와 그 조상들만 가능
< ? > 제한 없음(모든 타입 가능) <? extends Object>와 동일

package ch12;

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
{
	// 메서드 매개변수에도 와일드 카드 사용 가능 
	// 즉 Juicer.makeJuice(new FruitBox2< Apple or Grape>()))가능 
	static Juice makeJuice(FruitBox2<? extends Fruit2> box)
	{
		String tmp = "";
		
		for(Fruit2 f : box.getList())
			tmp += f + " ";
		return new Juice(tmp);
	}
}

public class Ex12_4 {

	public static void main(String[] args) {
		FruitBox2<Fruit2> fruitBox = new FruitBox2<Fruit2>();
		
		// 밑에서 만약 와일드 카드 사용했다면 
		// appleBox = new FruitBox2<Fruit2 or Apple2 or Grape2> 가 가능 
		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));
	}

}

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();}
	
}
profile
Journey for Backend Developer

0개의 댓글