정리/Java

자바의 정석 135~141강 ch12 지네릭스, 열거형, 애너테이션(1)

민발자 2023. 9. 12. 08:57
728x90

자바의 정석 기초편(2020최신)

ch 12-1 지네릭스 Generics 란

1. 지네릭스

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

// jdk 1.5 이전
ArrayList list = new ArrayList(); 
list.add(10);
list.add(20);
list.add("30"); // String 추가
Integer i = (Integer)list.get(2); // 컴파일  OK
/*
실행하면 에러, ClassCastException 발생, 형변환예외
컴파일러의 한계, 꺼낼때 Object라 컴파일에러 안뜨지만 실행시 형변환 에러발생
실행시 에러는 프로그램이 종료되고 컴파일 에러는 실행전에 에러가 뜨기때문에 컴파일 에러가 더 나음
지네릭스를 통해 컴파일 에러로 이끌어냄
*/

// jdk 1.5 이후 지네릭스 도입
ArrayList<Integer> genericList = new ArrayList<Integer>();
genericList.add(10);
genericList.add(20);
genericList.add("30"); // 저장 객체 타입을 지정해줬기때문에 컴파일 에러 발생

//꺼낼때 형변환 필요없음-지정해줬기 때문에 지정 타입으로 받아주면 됨
Integer i = genericList.get(1);

ArrayList<Object> objectList = new ArrayList<Object>();
// 여러 타입을 저장하고 싶으면 Object를 지정, 상황에 따라 형변환 필요

 

2. 지네릭스 장점

타입 안정성 제공, 타입체크와 형변환을 생략 가능해 코드 간결

 


ch 12-2~3 타입 변수

1. 타입변수

지네릭 클래스 작성 시, Object 타입 대신 타입 변수 E를 선언해 사용

 

2. 타입변수에 대입하기

객체 생성 시 타입변수 E 대신 실제 타입을 지정해 대입

ArrayList list = new ArrayList();
list.add(new Tv());
list.add(new Audio());
Tv t = (Tv)list.get(0); // list의 첫번째요소 꺼냄, 반환타입이 Object라 형변환필요

ArrayList<Tv> genericList = new ArrayList<Tv>(); // Tv객체만 저장 가능
genericList.add(new Tv());
// genericList.add(new Audio()); 타입변수를 지정해서 Tv객체 이외의 객체는 저장 불가
t = genericList.get(0); // 첫번째 요소 꺼냄, 반환타입이 모두 Tv라 형변환 불필요

ch 12-4~6 지네릭스 용어, 지네릭 타입과 다형성

1. 지네릭스 용어

Box<T> : 지네릭 클래스 T의 Box 또는 T Box라고 읽음
T : 타입 변수 또는 타입 매개변수
Box : 원시타입 raw type

 

2. 지네릭 타입과 다형성

참조변수와 생성자의 대입된 타입은 일치해야함, 부모 자식 관계여도 불일치하면 에러 무조건 일치해야함(다형성 안됨)

ArrayList<Tv> list = new ArrayList<Tv>();
ArrayList<Product> list = new ArrayList<Tv>(); // 에러

지네릭 클래스간 다형성은 성립, 대입된 타입은 일치해야함(다형성 안됨)

List<Tv> list = new ArrayList<Tv>(); // 지네릭 클래스간의 다형성은 가능, ArrayList가 List 구현
List<Tv> list = new LinkedList<Tv>(); // LinkedList가 List 구현

매개변수의 다형성 성립

ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv());
list.add(new Audio());
// Product의 자손인 Tv, Audio 가능

Product p = list.get(0); // 타입일치해 형변환 필요없음
Tv t = (Tv)list.get(1); // 저장되어있는 객체는 Tv객체이지만 반환타입이 Product이므로 형변환 필요

ch 12-7~8 Iterator, HashMap과 지네릭스

1. Iterator<E>

클래스 작성 시 Object타입 대신 타입 변수 사용

// 지네릭 도입 이전
Iterator it = list.iterator();
while(it.hasNext()) {
	Student s = (Student)it.next(); // 반환 타입이 Object 형변환 필요
    ...
}

// 지네릭 도입 이후
Iterator<Student> it = list.iterator();
while(it.hasNext()) {
	Student s = it.next(); // 반환 타입이 Student 형변환 불필요
    ...
}

 

2. HashMap<K, V>

타입 변수가 여러 개인 경우 ,를 구분자로 선언

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

Stuent s1 = (Stuent)map.get("1-1"); // 타입 불일치로 형변환 필요
Stuent s2 = map.get("1-1"); // 형변환 불필요

ch 12-9~11 제한된 지네릭 클래스, 지네릭스의 제약

1. 제한된 지네릭 클래스

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

class FruitBox<T extends Fruit> {} // Fruit의 자손만 타입으로 지정 가능

FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Toy> appleBox = new FruitBox<Toy>(); // Fruit의 자손이 아니기 때문에 에러

인터페이스도 extends이용해 제한 가능

interface Eatable {}
class FruitBox<T extends Eatable> {} // 대입할 수 있는 타입 제한

 

2. 지네릭스의 제약

타입 변수에 대입은 인스턴스 별로 다르게 가능, 생성할 때마다 다르게 지정 가능

static멤버에 타입 변수 사용 불가 - 모든 인스턴스에 공통이기 때문에

배열 생성할 때 타입변수 사용불가 - 생성자 new는 타입이 확정되어 있어야 함

Box<Apple> appleBox = new Box<Apple>(); // Apple만 저장 가능
Box<Grape> grapeBox = new Box<Grape>(); // Grape만 저장 가능
// 생성할 때마다 다르게 지정 가능

class Box<T> {
	static T item; // 에러 static멤버는 타입변수 사용 불가
    T[] itemArr; // T타입의 배열을 위한 참조 변수는 가능
    T[] toArray() {
		T[] tmpArr = new T[itemArr.length] // 에러 지네릭 배열 생성 불가 new T가 불가
		// 생성자 new는 타입이 확정되어 있어야 한다
	}
}

ch 12-12~14 와일드카드, 지네릭 메서드

1. 와일드 카드 <?>

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

<? extends T> 와일드 카드의 상한 제한, T와 그 자손만 가능
<? super T> 와일드 카드의 하한 제한, T와 그 조상만 가능
<?> 제한 없음 모든 타입 가능<? extends Object>와 동일
ArrayList<? extends Product> list = new ArrayList<Tv>(); // Product와 자손들(Tv, Audio)
ArrayList<? super Tv> list = new ArrayList<Tv>(); // Tv와 조상(Product, Object)

 

2. 메서드 매개변수에 와일드 카드 사용

static Juice makeJuice(FruitBox<? extends Fruit> box) { ... }
// 매개변수로 Fruit와 Fruit의 자손들 가능

 

3. 지네릭 메서드

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

클래스 타입 매개변수<T>와 메서드의 타입변수 <T>는 별개

class Box<T> { // 지네릭 클래스
	// 지네릭 메서드
	static <T> void sort(List<T> list, Comparator<? super T> c){ ... }
	// 타입문자 일치하나 다른 타입 변수
	// 지네릭 메서드 안에서는 메서드 타입변수가 우선 iv와 lv와 동일
}

메서드 호출할 때마다 타입을 대입(대부분 생략 가능)

메서드 호출 시 타입을 생략하지 않을 때는 클래스 이름 생략 불가(타입 생략하는 경우는 매우 드뭄)

System.out.println(Jucie.makeJuice(appleBox)); // 타입 생략가능
System.out.println(<Fruit>makeJuice(appleBox)); // 에러 타입 지정시 클래스 이름 필수 
System.out.println(Jucie.<Fruit>makeJuice(appleBox)); // 생략 안할 땐 클래스 이름 필수
System.out.println(this.<Fruit>makeJuice(appleBox)); // this 사용가능

 

4. 와일드카드와 지네릭 메서드 정리

와일드 카드는 하나의 참조변수로 서로 다은 타입이 대입된 여러 지네릭 객체를 다루기 위한 것

지네릭 메서드는 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것


ch 12-15~16 지네릭 형변환

1. 지네릭 타입의 형변환

지네릭 타입과 원시 타입 간의 형변환은 좋지 않음

Box1 box = null; // 원시타입 
Box1<String> bStr = null; // 지네릭 타입

box = (Box1)bStr; // 지네릭 -> 원시
bStr = (Box1<String>)box; // 원시 -> 지네릭
// 가능하지만 경고
box.add(new Integer(100));
// 참조변수가 원시타입이기 때문에 String 지네릭 타입이어도 Integer가 저장된다


Box1<Object> objBox = null;
Box1<String> strBox = null;

//		objBox = (Box1<Object>)strBox; 불가능 형변환 안됨
//		strBox = (Box1<String>)objBox; 불가능 형변환 안됨

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

// 와일드 카드 사용된 타입으로 형변환가능
Box1<? extends Object> wBox = (Box1<? extends Object>)new Box1<String>();
Box1<? extends Object> wBox = new Box1<String>(); // 형변환 생략가능

Box1<? extends Object> wBox2 = new Box1<Object>();
strBox = (Box1<String>)wBox2;
// 반대도 형변환 가능

 

2. 지네릭 타입의 제거

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

Object → <T>로 지정한 것을 다시 <T> → Object로 변경

자바 버전 하위 호환성때문에 컴파일러가 지네릭 타입을 제거함 → 지네릭 1.5부터 적용됨

 

①지네릭 타입의 경계를 제거

②지네릭 타입 제거 후 타입이 불일치하면 형변환 추가

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

728x90