반응형
1. 제네릭스( generics )
- 제네릭스는 다양한 타입의 객체들을 다루는 메소드나 컬렉션 클래스에 컴파일 시 타입체크( compile-time type check )를 해주는 기능이다.
- 객체의 타입은 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
- 타입 안정성을 높인다는 것은 의도하지 않는 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄어준다는 것이다.
12ArrayList<Integer> list = new ArrayList<Integer>();ArrayList<Integer> list2 = new ArrayList<>(); - 제네릭스의 장점
- 타입 안정성을 제공한다.
- 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
==> 다룰 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여준다는 것이다.
2. 제네릭 클래스의 선언
- 제네릭 타입은 클래스와 메소드에 선언할 수 있는데, 먼저 클래스에 선언하는 제네릭 타입에 대해서 알아보자.
123456class Box{Object item;void setItem(Object item){}Object getItem(){}}c - 위 클래스를 제네릭 클래스로 변경하고자 한다면 아래와 같이 '<T>'를 붙이고 Object는 모두 T로 변경하면 된다.
- Box<T>에서 'T'를 타입변수( type variable )라고 하며 'Type'에서 첫글자를 따온 것이다.( 타입변수는 T가 아닌 다른 것을 사용해도 된다. )
- 타입변수가 여러개인 경우에는 Map<K,V>와 같이 콤마를 구분자로 나열하면 된다.
- T, K, V 이들은 기호의 종류만 다를 뿐, '임의의 참조형 타입'을 의미한다는 것은 모두 같다.
- 기존에는 다양한 종류의 타입을 다루는 메소드의 매개변수나 리턴 타입으로 Object타입의 참조변수를 많이 사용했고, 그로 인해 형변환이 불가피했지만, 이젠 Object타입 대신 원하는 타입을 지정하기만 하면 되는 것이다.
1234Box<String> b = new Box<String>(); // 객체생성b.setItem(new Object()); // 에러 O, void setItem(String item){} String item = new Object();b.setItem("ABC"); // 에러 XString item = b.getItem // 형변환을 따로 할 필요가 없다는 장점cs - 제네릭이 도입되기 이전의 코드와 호환을 위해 제네릭클래스인데도 예전의 방식으로 객체를 생성하는 것이 허용되지만 제네릭타입을 지정하지 않아서 안전하지 않다는 경고가 발생한다.
123Box b = new Box(); ==> Box<Object> b = new Box<>();b.setItem(new Object()); // 에러 Xb.setItem("ABC"); // 에러 Xcs - 제네릭스 용어
class Box {} Box<T> 제네릭클래스, 'T의 Box' 또는 'T Box'라고 읽는다. T 타입변수 또는 타입 매개변수( T는 타입문자 ) Box 원시타입( raw type ) - 타입문자 T는 제네릭 클래스 Box<T>의 타입변수 또는 타입 매개변수라고 하는데 메소드의 매개변수와 유사한 점이 있기 때문이다.
- 그래서 아래와 같이 타입 매개변수에 타입을 지정하는 것을 '제네릭 타입 호출'이라고 하고 지정된 타입을 '매개변수화된 타입( parameterized type )'이라고 한다.
12Box<String> b = new Box<String>();Box<Integer> b1 = new Box<Integer>(); - 위처럼 Box<String>, Box<Integer>는 서로 다른 타입을 대입하여 호출한 것일 뿐 둘이 별개의 클래스를 의미하는 건 아니다.
- 제네릭스의 제한
- 제네릭 클래스 Box의 객체를 생성할 때는 객체 별로 다른 타입을 지정해주는 것이 적절하다.
왜? 제네릭은 인스턴스 별로 다르게 동작하도록 만든 것이기 때문이다. - 그러나 모든 객체에 대해 동일하게 동작해야 하는 static멤버에 타입변수 T를 사용할 수 없다.
왜? T는 인스턴스 변수로 간주되기 때문이다.
1234class Box<T>{static T item; // 에러static int compare(T t1, T t2){}; // 에러}cs - static 멤버는 타입 변수에 지정된 타입, 즉 대입된 타입의 종류에 관계없이 동일한 것이어야 하기 때문이다.
- 또한 제네릭 타입의 배열을 생성하는 것도 허용되지 않는다.
- 제네릭 배열 타입의 참조변수를 선언하는 것은 가능하지만 new T[10]; 과 같은 배열을 생성하는 것은 안된다.
==> new 연산자로 인해 불가능한 것인데, new 연산자는 컴파일 시점에 타입 T가 무엇인지 정확히 알아야 하기 때문이다.
- 제네릭 클래스 Box의 객체를 생성할 때는 객체 별로 다른 타입을 지정해주는 것이 적절하다.
3. 제한된 제네릭 클래스
- 타입문자로 사용할 타입을 명시하면 한 종류의 타입만 지정할 수 있도록 제한할 수 있지만, 그래도 여전히 모든 종류의 타입을 지정할 수 있다는 것에는 변함이 없다.
- 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한할 수 있는 방법이 있을까?
12Box<Toy> toyBox = new Box<>();toyBox.add(new Toy()); // Ok, 과일상자에 장난감을 담을 수 있다.cs - 아래와 같이 제네릭 타입에 extends를 사용하면 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
1class FruitBox<T extends Fruit>{/* 내용 생략 */}cs - 여전히 한 종류의 타입만 담을 수 있지만, Fruit 클래스의 자손들만 담을 수 있다는 제한이 더 추가된 것이다.
1234FruitBox<Toy> fruitBox = new FruitBox<>(); // 에러FruitBox<Fruit> fruitBox = new FruitBox<>();fruitBox.add(new Apple());fruitBox.add(new Grape());cs - 다형성에서 조상타입의 참조변수로 자손타입의 객체를 가리킬 수 있는 것처럼, 매개변수화된 타입의 자손타입도 가능한 것이다.
- 만일 클래스가 아니라 인터페이스를 구현해야 한다는 제약이 필요하다면 이 때도 implements가 아닌 extends를 사용한다.
1234interface Eatable{}class FruitBox<T implements Eatable> // 에러class FruitBox<T extends Eatable>class FruitBox<T extends Fruit & Eatable> // 둘다 상속하고 있는 타입만 가능
4. 와일드 카드( wild card )
- 매개변수에 과일박스를 대입하면 주스를 만들어 반환하는 Jucier라는 클래스가 있고, 이 클래스에는 과일을 주스로 만들어서 반환하는 static 메소드 makeJuice()가 있다고 가정하자.
123class Juicer{static Juice makeJuice(FruitBox<T> box){}}cs - Juicer 클래스는 제네릭 클래스가 아닌데다가 제네릭 클래스라고 해도 static 메소드에는 타입 매개변수 T가 사용할 수 없으므로 아예 제네릭스를 적용하지 않던가 아래와 같이 매개변수 대신 특정 타입을 정해줘야 한다.
12345678910class Juicer{static Juice makeJuice(FruitBox<Fruit> box){}static Juice makeJuice(FruitBox<Apple> box){} ==> 에러}FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();FruitBox<Apple> appleBox = new FruitBox<Apple>();Juicer.makeJuice(fruitBox); // 에러 XJuicer.makeJuice(appleBox); // 에러 Ocs - 이렇게 제네릭 타입을 'FruitBox<Fruit>'로 고정해 놓으면 위의 코드에서도 알 수 있듯이 'FruitBox<Apple>' 타입의 객체는 메소드의 매개변수가 될 수 없으므로 해당 메소드를 사용하기 위해선 오버로딩해야한다.
==> "제네릭 타입이 다른 것만으로는 오버로딩이 성립되지 않는다."라는 규칙때문에 우리가 생각한 것처럼 오버로딩을 하면 에러가 발생한다. - 이럴 때 사용하는 것이 와일드카드이다. 와일드카드는 기호 ?를 사용한다.
12345678
9class Juicer{static Juice makeJuice(FruitBox<? extends Fruit> box){} // Fruit 상속 받은 애 다 할 수 있음}FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();FruitBox<Apple> appleBox = new FruitBox<Apple>();
Juicer.makeJuice(fruitBox); // 에러 XJuicer.makeJuice(appleBox); // 에러 Xcs - 와일드카드는 기호 ?로 표현하는데 와일드카드는 어떠한 타입도 될 수 있다.
- ? 만으로는 Object 타입과 다를게 없으므로 아래와 같이 제한을 할 수 있다.
<? extends T> 와일드카드 상한제한, T와 그 자손들만 가능 <? super T> 와일드카드 하한제한, T와 그 조상들만 가능 <?> 제한 없음 모든 타입 가능( <? extends Object> )
5. 제네릭 메소드
- 메소드 선언부에 제네릭 타입이 선언된 메소드를 제네릭 메소드라 한다.
- 제네릭 타입의 선언 위치는 반환 타입 바로 앞이다.
1static<T> void sort(List<T> list, Comparator<? super T> c);cs - 제네릭 클래스에 정의된 타입 매개변수와 제네릭 메소드에 정의된 타입 매개변수는 전혀 별개인 것이다.
- 같은 타입문자 T를 사용해도 같은 것이 아니라는 것에 주의해야 한다.
- 메소드에 선언된 제네릭 타입은 지역변수를 선언한 것과 같다고 생각하면 편한데, 이 타입 매개변수는 메소드 내에서만 지역적으로 사용될 것이므로 메소드가 static이건 아니건 상관없다.
123456789static Juice makeJuice(FruitBox<? extends Fruit> box){}static<T extends Fruit> Juice makeJuice(FruitBox<T> box){}FruitBox<Fruit> fruitBox = new FruitBox<>();FruitBox<Apple> appleBox = new FruitBox<>();Juicer.<Fruit>makeJuice(fruitBox);Juicer.<Apple>makeJuice(appleBox);cs * 두 개는 같은 것이다.
static Juice makeJuice(FruitBox<? extends Fruit> box){}
static<T extends Fruit> Juice makeJuice(FruitBox<T> box){} - 그러나 대부분의 경우 컴파일러가 타입을 추정할 수 있기 때문에 생략해도 된다.
12Juicer.makeJuice(fruitBox);Juicer.makeJuice(appleBox);c
반응형
'웹개발 > JAVA' 카테고리의 다른 글
[JAVA] 파일 입출력, MVC 모델 (0) | 2022.04.15 |
---|---|
[JAVA] 쓰레드( Thread ) (0) | 2022.02.11 |
[JAVA] 컬렉션 프레임워크( Collection Framework ) - Arrays, Comparable & Comparator, HashSet, TreeSet, HashMap & HashTable (0) | 2022.02.04 |
[JAVA] 컬렉션 프레임워크( Collection Framework ) - ArrayList, LinkedList, Stack & Queue, Iterator (0) | 2022.02.03 |
[JAVA] Object, String, Math, Wrapper 클래스 (0) | 2022.01.29 |