[Java] 제네릭

이병수·2024년 1월 27일
0

Java

목록 보기
26/27
post-thumbnail

제네릭(Generic)


제네릭이란 무엇인가?

클래스 내부에서 사용할 데이터 타입외부에서 지정하는 기법을 의미한다.

우리가 흔히 데이터 타입을 사용할 때

public class Test {
	private String name;
    private int age;
    private Score myScore;
    private int money;
    
    ...
}

이런 식으로 데이터 타입클래스 내부 에서 먼저 지정을 한다.
따라서 해당 클래스를 사용하면 이미 고정된 타입을 사용해야한다는 것이다.

현재 money 타입은 int 타입으로 숫자의 범위 고정값이 21억 까지라고 가정을 해보자.

만약 내가 돈이 21억이 이상이라면? Test 클래스 내부에서 int 타입의 값 형식을 바꿔야하는 번거로움이 발생 할 수 있다.

또한, 이 뿐만 아니라 다른 타입들도 유동적으로 바뀌어야 할 경우
즉, 프로그래머가 타입을 지정해줘서 사용해줘야 한다면? 매번 그에 따른 타입의 클래스를 새로 만들어야하는 매~우 안좋은 코드가 될 수 있다.

이럴 때 제네릭(Generic)을 사용하면 된다.

public class Test <E> {
	private String name;
    private int age;
    private Score myScore;
    private E money;
}

멤버 변수인 money 타입이 E 타입으로 바뀌게 된다.

즉, 이렇게 하면 Test 클래스를 만들었을 때, 프로그래머가 임의로 타입을 지정하게 되면 money는 해당 타입으로 설정이 되는 것이다.

Test<Long> test = new Test();
test.set(200L); // Long타입으로 money 값을 지정해준다.
System.out.println(test.getMoney().getClass());

이렇게 해당 타입이 어떤 타입인지 확인한다면 결과값은 이렇게 나올 것이다.

class java.lang.Long

내가 Long 타입으로 지정을 해 주었기 때문에 money의 타입이 Long 타입으로 변경되는 것이다.


우리가 사용하는 컬렉션 중 하나인 List, Map, Queue, Set 또한 안의 코드를 확인하면 제네릭을 사용한다는 것을 확인할 수 있다.



제네릭(Generic) 타입

보통 제네릭 타입이 따로 정해진 것은 없다고 한다.
하지만 대중적으로 쓰이는 타입의 암묵적인 규칙이 있다고 한다.

그것이 바로 이 아래 표 타입들이라고 한다.

타입설명
< T >Type
< E >Element
< K >Key
< V >Value
< N >Number


제네릭(Generic)의 장점

일단 위에서 본 예시처럼 장점에 대해 이야기 하면 많다.


컴파일 타임에 타입 검사를 통한 예외를 방지할 수 있다.

잘못 된 타입을 프로그래머가 값을 지정해주었을 경우, 컴파일 단계에서 미리 해당 예외에 대한 오류를 발견하고 고칠 수 있다는 것이다.

우리가 자주 사용하는 모든 클래스의 조상 Object 타입을 사용해서 캐스팅을 통해 타입의 변환을 할 수 있지만, 만약 프로그래머가 잘못 캐스팅을 했을 경우를 생각해보자.

class Yong {}
class Older {}

class Person {
	private Object[] people;
    
    public Person(Object[] people) {
    	this.people = people;
    }
    
    public Object getPersons(int index) {
    	return people[index];
    }
}
Yong[] yongs = { new Yong(), new Yong(), new Yong() ...};

Person person = new Person(yongs);

Older olders = (Older)person.getPersons(0);	// 오류가 발생한다.

형 변환에도 문제가 발생하지 않지만, 이미 객체의 배열이 Yong 타입의 배열인데, 다른 타입으로 캐스팅을 하기 때문에 오류가 발생한다.

그리고 이 문제에 대해서는 컴파일 단계에서 미리 알려주지 않는다 (즉, 빨간 줄이 없다)

현재 이러한 단순한 예시가 아닌, 엄청나게 복잡하고 클래스들도 많고 줄이 길어지는데, 프로그램이 실행중이였다가 예외가 발생한다면 어떻게 될까??

결국에 어디에서 발생했는지 확인할 수 있지만, 그것도 일이다.

제네릭을 사용하게 된다면 이러한 문제를 컴파일 단계에서 미리 에러를 찾아 줄 수 있다.


타입을 체크하고 변환해줄 필요가 없다.

이미 제네릭을 통해 타입을 설정해준 뒤에 사용하기 때문에 형변환(Type Casting) 의 번거로움을 줄일 수 있고, 타입 검사에 들어가는 메모리 또한 줄일 수 있다.

그렇게 코드 자체가 줄어들며, 제네릭을 사용하면 가독성이 좋아진다.


비슷한 기능을 통한 코드의 재사용성이 높아진다.

만약 중복되는 기능이 필요한데, 타입만 다를 경우 어떻게 할까?

물론 Object 타입으로 변환하고 해당 타입을 캐스팅하여 사용할 수 있다.

그렇지만 번거롭다.
제네릭을 사용하면 이러한 기능을 바로 해결해 줄 수 있다.



제네릭 사용 주의사항

제네릭 타입의 객체 생성 불가

제네릭 타입 자체로 타입을 지정하여 객체를 생성하는 것은 불가능하다.

T t = new T();

static 멤버에 제네릭 타입은 올 수가 없다.

static이란 정적이라는 뜻이다.
static 변수, static 함수 등 static이 붙은 것들은 기본적으로 프로그램 실행 시에 메모리에 올라가 있는 상태이다.

이것이 의미하는 것은 바로 객체를 생성하기도 전에 이미 메모리에 올라가져 있는 상태란 말이다.
그래서 프로그래머가 타입을 지정하기도 전에 메모리에 올라가져 있는 상태이기 때문에 에러가 발생한다.

// static 메서드의 매개변수 타입으로 제네릭 사용 불가
public static void add(T n) {
}

// static 메서드의 반환 타입으로 제네릭 사용 불가
public static T add(int n) {
}

즉, 논리적인 오류인 것이다.


제네릭은 잘 사용한다면 정말 코드적으로 좋지만, 그만큼 어려운 문법이다.

profile
백엔드 개발자가 되고 싶어요

0개의 댓글