제네릭스

Drumj·2023년 2월 20일
0

오늘의 학습

Generics(제네릭스, 지네릭스)

다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능.
객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.

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

tvlist.add(new Tv()); //OK
tvList.add(new Audio()); //컴파일 에러, Tv 외에 다른 타입은 지정 불가.

위 코드와 같이 저장할 객체의 타입을 지정해주면, 지정한 타입 외에 다른 타입의 객체가 저장되면 에러가 발생한다.
또 저장된 객체를 꺼낼 때 형변환 할 필요가 없어서 편리하다.

제네릭스의 장점

  • 타입 안정성을 제공한다.
  • 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.

간단 용어 정리

class Box<T> {}

Box<T> : 제네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽는다.
T : 타입 변수 또는 타입 매개변수
Box : 원시 타입 (raw type)


제네릭 타입과 다형성

//1.
ArrayList<Tv> list = new ArrayList<Tv>(); //OK. 일치
ArrayList<Product> list = new ArrayList<Tv>(); //에러. 불일치

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

//2.
List<Tv> list = new ArrayList<Tv>(); //OK. 다형성 ArrayList가 List를 구현
List<Tv> list = new LinkedList<Tv>(); //OK. 다형성 LinkedList가 List를 구현

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

Product p = list.get(0);
Tv t = (Tv) list.get(1);
  1. 참조 변수에 지정해준 제네릭 타입과 생성자에 지정해준 제네릭 타입은 일치해야 한다.
    • Tv와 Product가 서로 상속 관계에 있어도 일치해야한다.
  2. 제네릭 타입이 아닌 클래스의 타입간에 다형성을 적용하는 것은 가능하다.
  3. ArrayList에 Product의 자손 타입을 저장 할 수 있다.
    • 단, 꺼낼 때는 형변환이 필요하다.

제한된 제네릭 클래스

타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한할 수 있는 방법은 없을까?

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

위 코드와 같이 제네릭 타입에 extends를 사용하면 특정 타입의 자손들만 대입할 수 있게 제한 할 수 있다.

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

또한 인터페이스를 구현해야한다는 제약이 있어도 implements를 사용하지 않고 extends를 사용한다.

클래스 Fruit의 자손이면서 인터페이스 Eatable 도 구현해야 한다면 &을 사용하면 된다.

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

와일드 카드

제네릭 타입에 와일드 카드를 사용하면 다형성을 적용할 수 있다.

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

//Tv 와 Audio가 Product의 자손이라 가정
//제네릭 타입이 `? extends Product` 이면, Product와 Product의 모든 자손이 OK
ArrayList<? extends Product> list = new ArrayList<Tv>();
ArrayList<? extends Product> list = new ArrayList<Audio>();

제네릭 메서드

메서드의 선언부에 제네릭 타입이 선언된 메서드.
반환 타입 바로 앞에 제네릭 타입이 선언 된다.

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

static 멤버에는 타입 매개변수를 사용할 수 없지만 메서드에 제네릭 타입을 선언하고 사용하는 것은 가능하다.

제네릭 메서드를 호출할 때, 대입된 타입을 생략할 수 없는 경우에는 참조변수나 클래스 이름을 생랴갈 수 없다는 것을 주의하자.

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

같은 클래스 내에 있는 멤버들끼리는 참조변수나 클래스이름, 즉 'this.'나 '클래스이름.' 을 생략하고 메서드 이름만으로 호출이 가능하지만, 대입된 타입이 있을 때는 반드시 써줘야 한다.


이상 자바의 정석을 공부하면서 간단하게 정리했다.
사용 방법은 여러 강의를 들으면서 대충 이해했었고 책을 보면서 용어나 사용할 수 없는 경우 등을 알았다. 생각보다 간단(?)했다. <T> 만 잘 사용하면 아직까지는 별 문제없이 개발 할 수 있을 것 같다.

0개의 댓글