● 지네릭스
○ 지네릭스 : 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크(compile - time type check)를 해주는 기능
■ 객체 별로 다른 타입을 지정해서 쓸 수 있게 하는 기능
■ 객체의 타임을 컴파일 시 체크 -> 객체의 타입 안정성 높이고 번거로움 줄임
■ 타입 안정성을 높임 : 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래 타입과 다른 타입으로 잘못 형 변환되어 발생할 수 있는 오류 줄여줌
■ 지네릭스 장점
(1) 타입 안정성 제공
(2) 타입 체크와 형 변환 생략 가능 -> 코드 간결해짐
○ 지네릭 클래스 선언
■ 클래스 옆에 <T> 붙이기
class Box<T>{
T item;
void setItem(T item){ this.item = item; }
T getItem(){ return item; }
}
■ <T> 안의 T : 타입 변수(Type)
T 말고도 E(Element_요소), K(Key_키), V(Value_값)
임의의 참조형 타입을 의미하는 것!
Object타입 대신 원하는 타입 지정하면 됨
Box<String> b = new Box<String>(); // 이런 식
■ 지네릭스 용어
□ Box<T> : 지네릭 클래스. “T의 Box”, “T Box” 라고 읽음
□ T : 타입 변수 or 타입 매개변수\
□ Box : 원시 타입(raw type)
□ 지네릭 타입 호출 : 매개변수에 타입을 지정하는 것
□ 대입된 타입 = 매개변수화 된 타입 : 지정된 타입, <> 안에 들어가는 타입
■ 지네릭스의 제한
□ 모든 객체에 대해 동일하게 동작해야 하는 static 멤버에 타입 변수 T 사용 불가능
T는 인스턴스 변수로 간주 됨
□ 지네릭 타입의 배열을 생성하는 것 허용하지 않음
지네릭 배열 타입의 참조변수 선언은 가능하지만 new T[10]; 처럼 배열 생성 불가능
instanceof 연산자와 new 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 함
지네릭 배열 생성해야 한다면 newInstance() 와 같이 동적으로 객체 생성하는 메서드로 배열 생성하거나 Object 배열 생성해 복사한 다음 T[]로 형 변환
○ 지네릭 클래스의 객체 생성과 사용
■ 객체 생성 시 참조변수와 생성자에 대입된 타입(매개변수화 된 타입)이 일치해야 함
■ 두 지네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같으면 괜찮음
Box<Apple> applebox = new Box<Apple>(); // 됨
Box<Apple> applebox = new Box<Grape>(); // 안 됨
Box<Fruit> applebox = new Box<Apple>(); // 가능. 다형성
■ JDK1.7 이상부터는 추정 가능한 경우 타입 생략 가능
○ 제한된 지네릭 클래스
■ 지네릭 타입에 extends 붙이면 특정 타입의 자손들만 대입 가능
class FruitBox<T extends Fruit> { … } // 해당 클래스는 Fruit 클래스 자손들은 받을 수 있음
■ 인터페이스를 구현해야 하더라도 extends 사용(implements 아님)
■ 클래스와 인터페이스를 함께 구현하는 자식인 경우 &로 둘 연결해 줌
class FruitBox<T extends Fruit & Eatable> extends Box<T>{ }
// Box<T>의 자손 클래스 FruitBox는 타입 매개변수로 Fruit과 Eatable 을 구현한 자손 클래스 받음
● 와일드 카드
○ ?
■ <?> : <? extends Object>
<> : <Object>
■ static 메서드에서는 타입 매개변수 T를 사용할 수 없음
■ 지네릭 타입이 다른 것 만으로는 오버로딩이 성립하지 않음! -> 단순 메서드 중복 정의 에러임
static 메서드에 지네릭 타입이 다른 매개변수 쓴다고 해서 되는 게 아님
○ ?의 상한과 하한_extends super
■ <? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
■ <? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
■ <?> : 제한 없음. 모든 타입이 가능. =<? extends Object>
□ static Juice makeJuice(FruitBox<? extends Fruit> box){ …. return new Juice(box); }
→ 호출 Juicer.makeJuice(fruitBox); (클래스명.메서드명(매개변수))
Juicer 클래스 안에 static 메서드로 지정되어 있다고 할 때
이런 식의 static 메서드의 매개변수를 상한을 정함
Fruit의 자손들만 받는 FruitBox 클래스를 매개변수로 받게 함
□ static <T> void sort(List<T> list, Comparator<? super T> c){}
→ 호출 Collections.sort(appleBox.getList(), new AppleComp());
Collections.sort() 메서드!
List <T> list : 정렬할 대상
Comparetor <? super T> : 정렬할 방법. 지네릭 하한 제한 걸려있는 와일드 카드 사용됨
Comparator의 지네릭을 해당 타입 매개변수 조상까지 쓸 수 있다는 것
Comparator<Apple> 이라면
Comparator<Object>, Comparator<Fruit>, Comparator<Apple> 된다는 말이다
Comparator<Grape>는 안됨! 조상이 아니니까!(형제…. 클래스 ㅋㅋ 잖아)
○ 지네릭 메서드
■ 메서드의 선언부에 지네릭 타입이 선언된 메서드
반환타입 바로 앞에 <T> 써줌
■ 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 별개!
지네릭 메서드는 지네릭 클래스가 아닌 클래스에서도 정의 가능
■ static 멤버에는 타입 매개변수 사용할 수 없지만 메서드에 지네릭 타입 선언하고 사용하는 것은 가능함
■ 메서드에 선언된 지네릭 타입은 매개변수의 지네릭 타입을 뽑아내서 써주는 것
메서드 내에서만 지역적으로 사용되므로 지역변수를 선언한 것과 같다고 생각하면 됨
내부 클래스에 선언된 타입 문자가 외부 클래스의 타입 문자와 같아도 구별 가능
static Juice makeJuice(FruitBox<? extends T> box){…}
→ static <T extends Fruit> Juice makeJuice (FruitBox<T> box){…}
→ 호출 Juicer.<Fruit>makeJuice(fruitBox); or Juicer.<Apple>makeJuice(appleBox);
■ 대입된 타입을 컴파일러가 추측할 수 있다면 대입된 타입 생략 가능
Juicer.makeJuice(fruitbox);
■ 대입된 타입을 생략할 수 없는 경우는 참조변수나 클래스 이름 생략할 수 없음
같은 클래스 내 멤버끼리는 참조변수나 클래스이름(this, 클래스이름)을 생략하고 메서드 이름만으로 호출 가능하지만, 대입된 타입이 있을 때는 반드시 써줘야 함(규칙임)
■ 매개변수 타입이 요란할 때 코드 간략히 할 수 있음
public static void printAll(ArrayList<? extends Products> list, ArrayList<? extends Products> list2){ } 이런 것들을
è public static <T extends Product> void printAll(ArrayList<T> list, ArrayList<T> list2){ }
이렇게 간략화 할 수 있음
■ 복잡한 와일드 카드의 지네릭 메서드 분석하기
□ public static <T extends Comparable<? super T>> void sort(List<T> list){}
□ 매개변수 안의 List <T> : 타입 T를 요소로 하는 List 매개변수로 허용
□ 지네릭 메서드 선언의 <T extends Comparable <? super T>>
→ <T extends Comparble> : T는 Comparable 구현한 클래스
→ Comparable<? super T> : Comparable은 T와 그 조상 타입을 비교하는 인터페이스임
□ 즉 List는 T를 사용한 Collections이고 그 T는 Comparable 구현한 클래스이며, Comparable은 T와 그 조상들을 비교하는 인터페이스! 가 되는 것
□ 와일드 카드부터 내려놓고 차근차근 뜯어보자
● 지네릭 타입의 형 변환과 지네릭 타입 제거
○ 지네릭 타입의 형 변환
■ 지네릭 타입과 넌지네릭 타입 간의 형 변환은 항상 가능 -> 그러나 경고 발생!
Box box1 = null;
Box<Object> box2 = null;
box1 = (Box)box2; // 경고 발생 (원시 타입으로 형 변환)
box2 = (Box<Object>)box2; // 경고 발생 (지네릭 타입으로 형 변환)
■ 대입된 타입이 다른 지네릭 타입 간의 형 변환은 불가능
Box<String> box1 = null;
Box<Object> box2 = null;
box1 = (Box<String>)box2; // 에러
box2 = (Box<Object>)box2; // 에러
■ 와일드 카드 사용한 대입 타입간 형 변환은 가능 -> 다형성
Box<? extends Object> box2 = new Box<String>(); // 다형성 적용
Box <String > box2 = new Box <? extends Object >(); // 가능은 하지만 경고 발생
■ 즉, 지네릭 타입 다른 것들은 형 변환 직접 X 와일드 카드 포함된 지네릭으로 형 변환 가능
■ 와일드 카드가 사용된 지네릭 타입끼리도 상속관계가 있다면 형 변환은 가능
○ 지네릭 타입의 제거
■ 컴파일러는 지네릭 타입을 이용해 소스 파일을 체크하고, 필요한 곳에 형 변환을 넣어준 뒤 지네릭 타입 제거함
■ 제거과정
(1) 지네릭 타입의 경계(bound) 제거
(2) 지네릭 타입을 제거한 후 타입이 일치하지 않으면 형 변환 추가
와일트 카드가 포함되어 있는 경우 적절한 타입으로 형 변환 추가 됨
'JAVA > 이론' 카테고리의 다른 글
[java][이론]014 컬렉션 프레임웍_List (0) | 2020.08.28 |
---|---|
[java][이론]014 컬렉션 프레임웍_개괄 (0) | 2020.08.28 |
[java][이론] 013 예외처리 (0) | 2020.08.12 |
[java][이론] 012 추상클래스와 인터페이스 (0) | 2020.08.12 |
[java][이론] 011 다형성 (0) | 2020.08.06 |