제네릭 ㄴㅇㄱ

cutiepazzipozzi·2023년 3월 15일
4

지식스택

목록 보기
6/35
post-thumbnail

도서관에서 자바 공부를 하고 싶어 이펙티브 자바를 빌렸는데 제네릭에 대한 소개가 있었는데 제네릭? 이게 모람? 이러고 개념에 대해 찾아보는 포스팅이다.

제네릭? 이라고 한국말로 보면 몬 소린가 싶은데, Generic으로 우리가 익숙하게 사용했던 general(일반적인)와 비슷한 의미를 지니는 듯 싶다.

제네릭?

= 데이터 타입을 일반화 한다!

  • 타입 매개변수 = 제네릭 클래스, 인터페이스
  • 우리가 흔히 보던 List<Integer> list = new ArrayList<>()
    타입은 Integer로 명시해준 하나의 예시라고 볼 수 있다.

제네릭 용어들

List<String> 매개변수화 타입
List<?>		 비한정적 와일드 카드
//실제 매개변수 타입이 무엇인지 모르지만 알 필요 없을 때
//그저 타입 안정성을 위해
List 		 로 타입
E 			 정규 타입 매개변수
List<E> 	 제네릭 메서드

와일드 카드

알 필요가 없다면 그만큼 의미 없다는 뜻일까, 도대체 이 친구가 갖는 존재의 이유가 뭘까 하고 제네릭에 관해 이것저것 읽어보다가
와일드 카드는 불공변성 때문에 유연성이 떨어지는 단점을 보완하기 위해 등장했다는 사실도 알았다.

동시에 타입 인자에 제약을 줄 때 extendssuper이 사용된다.

  • <? extends RemoteController> = RemoteController의 하위 타입을 받음 = Upper Bounded Wildcard
  • <? super RemoteController> = RemoteController의 상위 타입을 받음 = Lower Bounded Wildcard

합쳐서 PECS(= Producer Extends Consumer Super)로,
데이터를 생산하는 컴포넌트에서는 extends를, 데이터를 소비(저장,수정..)하는 컴포넌트에서는 super를 사용한다.

예시를 들어보자.

public class Remote<Device> {
	private Device dev;
    public Remote(Device dev) {
    	this.dev = dev;
    }
    public Device getDev() {
    	return dev;
    }
}
public class RemoteController {}
public class TvRemoteController {
	private int number;
    public TvRemoteController(int number) {
    	this.number = number;
    }
    public int getNumber() {
    	return number;
    }
}
public class AirRemoteController {
	private int temp;
    public AirRemoteController(int temp) {
    	this.temp = temp;
    }
    public int getTemp() {
    	return temp;
    }
}

TvRemoteController tv = new TvRemoteController(10);
AirRemoteController air = new AirRemoteController(26);
Remote<RemoteController> tvrc = new RemoteController<TvRemoteController>(tv)
//이렇게 쓰이면 컴파일에러!
//왜? 제네릭 타입의 불공변성 때문에 상하위 타입 관계가 성립되지 않음

//그래서 와일드 카드를 이용해본다
Remote<?> tv = new Remote<TvRemoteController>(tv);

//RemoteController의 하위 타입을 받을 수 있으므로
Remote<? extends RemoteController> tvct = new Remote<TvRemoteController>(tv);

//그러나
List<TvRemoteController> trc = new ArrayList<>();
List<? extends RemoteController> rc = trc;
list.add(new TvRemoteController(15)); //는 성립하지 않는다
//왜? extends면 참조하는 객체가 Tv이건 Air이건 extends 때문에 
//RemoteController의 하위 타입이 올 수 있으므로
//Air일 수도 있기 때문에 위 객체의 add를 허락하지 않음!

어떻게 쓰는데?

아래는 제네릭에서 흔하게 쓰이는 타입의 일종이다.

E – Element // E가 보통은 가장 많이 쓰임!
K – Key
N – Number
T – Type
V – Value

그래서 어떻게 일반화를 해주냐면, 제네릭 클래스의 경우로 보자.

public class Student<T> {
	private T age;
    
    public void setAge(T age) {
    	this.age = age;
    }
    
    public T getAge() {
    	return this.age;
    }
}

이렇게 요소를 T라고 설정해주고 실제로 클래스를 선언할 때에는

Student<Integer> stu = new Student<Integer>(); 

이렇게 구체적인 타입을 명시해준다.

여기서 주의해야 할 점이 있다면 이전에 설명했던 타입의 기본형(ex.int)은 사용되지 못해 참조형으로 바꿔(ex.Integer) 타입에 넣어주어야 한다.

이 말은 즉슨 우리가 따로 선언한 클래스도 타입에 들어갈 수 있다는 것!

public class Student {
	int age;
    String name;
	public Student(int age, String name) {
    	this.age = age;
        this.name = name;
    }
}
public class main(String[] args) {
	PriorityQueue<Student> pq = new PriorityQueue<>();
    //요렇게!
}

왜 좋은데?

  1. 컴파일 시 잘못된 타입이 들어오는 것을 미리 방지할 수 있음
    = Type Safety
  2. 비슷한 기능을 사용할 때 재사용성이 높아짐

참고

https://st-lab.tistory.com/153
https://eratosthenes.tistory.com/m/13
https://dev.gmarket.com/28
https://daily-study.tistory.com/1 한번 읽어보기

profile
노션에서 자라는 중 (●'◡'●)

0개의 댓글