자바 제네릭이란 | 제네릭의 개념과 활용, 제네릭 타입의 선언 방법

자바 제네릭이란
자바 제네릭이란

 

자바 제네릭

1. 자바 제네릭이란

1.1. 제네릭의 개념과 필요성

제네릭은 자바의 핵심 기능 중 하나로, 컴파일 시에 타입을 체크하여 타입 안정성을 보장하는 기능입니다. 제네릭을 사용하면 미리 정의한 타입 매개변수에 따라 클래스나 메서드를 작성할 수 있습니다.

2. 자바 제네릭의 주의사항

2.1. 널 포인터 예외 처리하기

자바 제네릭을 사용할 때 주의해야 할 사항 중 하나는 널 포인터 예외를 처리하는 방법이다. 제네릭을 사용하면 타입 안정성이 높아지기 때문에 코드의 안정성이 높아질 수 있지만, 동시에 널 포인터 예외가 발생할 수 있는 가능성도 존재한다. 예를 들어, 제네릭 타입이 파라미터로 전달되는 메서드를 호출할 때, 해당 인자가 널(null)일 경우 예외가 발생할 수 있다.

널 포인터 예외를 처리하기 위해 해결책으로는 메서드 내부에서 널 여부를 체크하는 방법이 있다. 예를 들어, Objects 클래스의 requireNonNull() 메서드를 사용하여 파라미터가 널인 경우 예외를 발생시킬 수 있다. 이 외에도 if문을 사용하여 널 여부를 체크하는 방법이 있으며, 각 경우에 따라 적합한 방법을 선택해야 한다.

2.2. 로 타입 사용 주의하기

로(raw) 타입은 제네릭 타입에서 타입 인자를 생략한 것을 의미한다. 로 타입을 사용하는 것은 컴파일 단계에서 경고를 일으킬 수 있다. 로 타입을 사용하는 것은 제네릭의 타입 안정성을 잃을 수 있기 때문에 주의해야 한다.

로 타입의 예를 들어보자. ArrayList 클래스는 제네릭 타입을 사용하여 요소의 타입을 지정할 수 있는데, 로 타입으로 선언하여 사용하면 타입 안정성을 잃어버린다.

“`java
ArrayList list = new ArrayList(); // 로 타입 사용
list.add(“example”);
list.add(123); // 컴파일 경고 발생
“`

위의 코드에서는 ArrayList가 로 타입으로 선언되었기 때문에 list에는 모든 종류의 객체가 추가될 수 있다. 따라서 컴파일 경고가 발생하게 된다.

로 타입은 되도록 피하고, 제네릭 타입을 명시적으로 지정하여 타입 안정성을 확보해야 한다.

2.3. 제네릭 타입 추론의 한계

자바의 제네릭에서는 타입의 인자를 명시적으로 지정하지 않아도 컴파일러가 타입을 추론할 수 있다. 이를 타입 추론이라고 한다. 하지만 제네릭을 사용할 때 모든 상황에서 항상 타입을 추론할 수 있는 것은 아니다. 때때로 타입 추론이 실패할 수 있는데, 이는 컴파일러가 타입을 정확히 추론할 수 없기 때문이다.

타입 추론의 한계를 이해하고, 타입이 명확하지 않은 경우에는 명시적으로 타입을 지정하는 것이 좋다. 타입을 명시적으로 지정하는 것은 코드의 가독성을 높이고, 오류를 방지하는 역할을 한다.

3. 제네릭 메서드 활용 팁

3.1. 와일드카드 사용하기

와일드카드는 알려지지 않은 타입을 지정하는데 사용되는 특수한 기호이다. 제네릭 메서드에서 와일드카드는 불특정한 타입으로 타입 인자를 대체할 수 있다. 와일드카드를 사용하면 다양한 타입의 인자를 처리하는 유연성을 얻을 수 있다.

와일드카드의 예를 살펴보자. 아래의 메서드는 List를 입력받아 요소를 출력하는 기능을 한다.

“`java
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
“`

위의 코드에서 `List<?>`는 List의 요소 타입을 모르는 상태를 나타내는 와일드카드이다. 따라서 모든 종류의 List를 인자로 전달할 수 있다.

3.2. 상위 제한 타입 사용하기

제네릭 메서드에서 상위 제한 타입(upper bounded wildcards)은 특정 타입의 하위 타입들만 인자로 받을 수 있도록 제한하는 기능이다. 특정한 상위 클래스나 인터페이스의 하위 타입들만 사용할 수 있도록 제한할 때 유용하게 사용된다.

예를 들어, 아래의 메서드는 Number 클래스와 그 하위 타입들만 인자로 받을 수 있다.

“`java
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
“`

위의 코드에서 `List<? extends Number>`는 Number 클래스 및 그 하위 타입들만을 요소로 갖는 List를 나타낸다. 따라서 Integer, Double 등 Number의 하위 타입이 인자로 전달될 수 있다.

3.3. 타입 추론 기능 활용하기

자바의 제네릭 메서드는 타입 추론 기능을 활용하여 보다 편리하게 사용될 수 있다. 타입 추론은 컴파일러가 인자의 타입을 추론하여 자동으로 타입을 지정하는 기능이다. 타입 추론을 활용하면 제네릭 메서드를 보다 간결하게 작성할 수 있다.

타입 추론의 예를 살펴보자. 아래의 예제에서는 Collections 클래스의 정적 메서드인 emptyList()를 호출하여 빈 리스트를 생성하고 있다. 이때, 타입 추론을 사용하여 타입 인자를 생략할 수 있다.

“`java
List list = Collections.emptyList(); // 타입 추론 활용
“`

위의 코드에서 `Collections.emptyList()`는 빈 리스트를 생성하며, 이때 유추된 타입이 String 타입인 것을 컴파일러가 추론하여 타입 인자를 생략할 수 있다.

4. 자바 제네릭 관련 자주 하는 질문

4.1. 제네릭 타입 지원 여부

자바에서는 JDK 5부터 제네릭 타입을 지원한다. 제네릭 타입은 클래스나 메서드를 정의할 때 타입 매개변수를 사용하여 재사용성과 타입 안정성을 제공한다. 따라서 JDK 5 이상의 버전에서는 제네릭 타입을 활용하여 개발할 수 있다.

4.2. 제네릭 성능에 미치는 영향

제네릭 타입은 타입의 안정성을 보장하기 위해 컴파일 단계에서 추가적인 타입 체크 작업을 수행한다. 이로 인해 실행 시간에는 일부 성능 저하가 발생할 수 있다.

제네릭 타입의 성능에 영향을 미치는 요소로는 박싱/언박싱 연산, 오버헤드 및 메모리 사용량 등이 있다. 따라서 성능에 민감한 경우에는 제네릭을 사용하기보다는 로 타입을 고려해야 할 수도 있다. 하지만 타입 안정성을 확보하기 위해서는 제네릭을 사용하는 것이 좋다.

4.3. 제네릭 타입으로의 형변환

자바에서는 제네릭 타입 간의 형변환이 가능하다. 이는 제네릭 타입의 타입 인자를 다른 타입으로 변환하는 것을 의미한다.

예를 들어, List은 List의 하위 타입이기 때문에 List로 형변환할 수 있다.

“`java
List list = new ArrayList<>();
List<?> wildcardList = list; // 제네릭 타입으로의 형변환
“`

위의 코드에서는 List을 List<?>로 형변환하고 있다. 이처럼 제네릭 타입 간의 형변환을 활용하면 유연한 코드를 작성할 수 있으나, 주의해야 할 점은 제네릭 타입의 타입 안정성을 유지하는 것이다.

Leave a Comment