새소식

Java

9. Generic

  • -

 

서론

어디선가 자바로 만든 코드를 본 적이 있다면 ArrayList <> 같은 형식의 문장을 본 적이 있을 것이다.

저 <>안에 내가 원하는 타입을 넣으면 그 타입에 맞는 객체가 생성이 되는데 이러한 방식이 Generic이다.

 

Generic

 

Generic은 데이터 형식에 의존하지 않고, 하나의 값이 여러 개의 다른 데이터 타입을 가지도록 해주는 방법이다.

이걸 조금 더 있어 보이게 표현하자면 컴파일 때 타입을 체크하게 해주는 방법이다.

Generic은 객체의 타입에 대한 안전성을 높이고, 형 변환의 번거로움을 줄이기 위해 사용된다.

가 표면적인 이유이지만, 한 가지 예시를 들어 생각해 보면 왜 쓰는지 바로 이해할 수 있다.

서론에서 언급한 ArrayList의 타입이 만약 지정되어 있다면, 개발자는 수많은 타입의 ArrayList를 정의해야 할 것이다. 너무 비효율적이고 귀찮기 때문에 나온 거다.

 

public class GenericBox<T> {  //<T>로 만들어 Generic으로!
	private T some; 

	public GenericBox() {}
	
	public GenericBox(T some) {
		this.some = some;
	}


	public T getSome() {
		return some;
	}

	public void setSome(T some) {
		this.some = some;
	}
}



public static void main(String[] args) {
       GenericBox<String> gbox = new GenericBox<>();
        
        gbox.setSome(1); // String으로 정의 했기 때문에 에러 발생
        gbox.setSome("Generic Test");
     	System.out.println("문자열: " + gbox.getSome());
}

 

 

 

 

하지만 우리는 이전에 Object라고 하는 조상님 클래스에 대해 배웠다. Object가 이런 역할을 충분히 수행해 줄 것 같은데 어떤 차이점이 있을까?

 

 

 

첫 번째로 Object는 처음 제작 때 모든 타입을 받을 수 있게 해 준 것이고, Generic은 사용자가 필요할 때 타입을 지정하고 사용하는 방식이다. 요약하자면 Generic은 Object와 달리 사용자가 타입을 결정한다.

 

두 번째로 형변환의 차이가 있다. 예를 들어 Object 타입의 자료구조에 int형 숫자를 하나 넣었다고 가정하자.

이 숫자는 나중에 사용을 할 때 꺼내도 선언된 타입이 Object이기 때문에 사용할 때 다시 int형으로 형변환을 해주어야 한다. 하지만 Generic은 자료구조의 선언 당시 int를 지정된 상태이고, 이에 숫자만 저장이 가능해지기 때문에 형변환이 따로 필요 없이 편하게 쓸 수 있다는 차이점이 있다.

 

 

 

 

Generic 주의사항

1. 제네릭 타입의 객체는 생성이 안된다.

2. Static이랑은 같이 사용 못한다.

  - 이는 사용자가 타입을 집어넣기 전 선언이 되어버리기 때문이다.

3. 타입에 넣어둔 객체는 상속관계라도 참조할 수 없다. -> 타입은 무조건 일치해야 한다.

ex) Box <A> box = new Box <B>(); 에서 A와 B가 상속관계에도 성립이 안된다.

4. 배열과 Generic은 같이 못 쓴다. (사실 가능은 하지만 안 쓰는 것이 좋다.)

  - 배열은 런타임에 실체화가 되고, Generic은 런타임 때 타입이 소거되므로, 타입의 안전성을 보장할 수 없다.

 

 

 

 

Generic Method

말 그대로 파라미터와 리턴 타입을 Generic 형식으로 가지고 있는 메서드이다.

이 예시와 같이 사용하면 되고 메서드가 호출되는 시점에서 <P>의 타입을 결정한다.

 

 

 

 

공변? 불공변?

Generic에 대한 정보를 어디서 찾아보게 되면 공변, 비공변이라는 말이 간혹 보일 것이다. 공변과 불공변은 다음과 같이 정의한다.

 

공변: A가 B의 하위 타입일 때, T <A>가 T <B>의 하위 타입이면 T는 공변

불공변: A가 B의 하위 타입일 때, T<A>가 T<B>의 하위 타입이 아니라면 T는 불공변

 

 

우리가 본 Generic은 불공변이고, 배열은 공변이라고 보면 된다.

    void GenericTest(){
        Integer[] integers = {1,2,3};
        printArray(integers);
        
        ArrayList<Integer> list = new ArrayList<>();
        printlist(list); // Integer의 부모 클래스에 Object가 있지만 불공변이기에 불가능
    }
    void printArray(Object[] arr){
        for (Object o : arr) System.out.println(o);
    }
    void printlist(Collection<Object> arr){
        for (Object o : arr) System.out.println(o);
    }

 

이러면 오히려 Generic을 써야 할 이유가 사라지지 않을까? 해서 나온 게 와일드카드라는 것이다.

 

 

와일드카드 <?>

생긴 거 같이 무슨 타입인지 모를 때 사용하는(모든 타입을 대신할 수 있는) 타입이다.

불공변인 Generic의 타입을 와일드카드로 처리하게 되면서, 위와 같은 에러를 해결할 수 있게 되었다.

하지만 와일드카드를 이용해 ArrayList를 만들게 된다면, 어떤 클래스든지 들어가 버리기 때문에 타입 안전성이 깨지게 되어 요소를 추가하는 연산은 에러를 발생시키게 된다.

 

그래서 JAVA에서는 이러한 문제를 해결하기 위해 와일드카드의 범위를 지정해 주는 기능을 구현하였는데, 상한선과 하한선을 이용해 와일드카드의 범주를 조절할 수 있게 되었다.

 

 

 

 

 

 

 

'Java' 카테고리의 다른 글

11. Lambda  (0) 2024.07.24
10. Comparable? Comparator?  (3) 2024.07.23
8. 추상  (1) 2024.07.20
7. 다형성  (2) 2024.07.18
6. 코드 외 작성할 것들  (0) 2024.07.17
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.