CS/Java

Generic

leah-only 2025. 4. 23. 19:42

Generic (제네릭) 이란? 

자바에서 제네릭이란 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 말한다. 

우리가 변수를 선언할 때 변수의 타입을 지정해주듯이, 제네릭은 객체에 타입을 지정해주는 것이라고 보면 된다. 

예를 들어 리스트 자료형 같은 컬렉션 클래스나 메소드에서 사용할 내부 데이터 타입을 파라미터 주듯이 외부에서 지정하는 이른바 타입을 변수화 한 기능이라고 이해하면 된다. 

 


Generic 타입 기호 네이밍

타입 설명
<T> 타입 (Type)
<E> 요소 (Element), 예를 들어 List
<K> 키 (Key), 예를 들어 Map<k,v>
<V> 리턴 값 또는 매핑된 값 (Variable)
<N> 숫자 (Number)
<S,U,V> 2번째, 3번째, 4번째에 선언된 타입

제네릭 선언

1. 클래스 및 인터페이스 선언

public class ClassName <T> { ... }
public Interface InterfaceName <T> { ... }

 

T 타입은 해당 블럭 { ... } 안에서까지만 유효하다. 

 

2. 복수 타입 파라미터 

public class ClassName <T, K> { ... }
public Interface InterfaceName <T, K> { ... }

// HashMap의 경우
public class HashMap <K, V> { ... }

 

3. 중첩 타입 파라미터

// LinkedList<String>을 원소로서 저장하는 ArrayList
ArrayList<LinkedList<String>> list = new ArrayList<LinkedList<String>>();

 

제네릭 객체를 제네릭 타입 파라미터로 받는 형식도 표현 가능하다. 

 

4. 객체 생성 

public class MyClass <T, K> { ... }

public class Main {
    public static void main(String[] args) {
        MyClass<String, Integer> a = new MyClass<Strnig, Integer>();
    }
}

 

생성된 제네릭 클래스를 사용해 객체를 생성할 때, 구체적인 타입을 명시해야 한다. 

또한 파라미터로 명시할 수 있는 것은 참조 타입(Reference Type) 밖에 올 수 없다. 

 

jdk 1.7 버전부터, new 생성자 부분의 제네릭 타입을 생략할 수 있게 되었다. 

MyClass<String, Integer> a = new MyClass<Strnig, Integer>();
// new 생성자 부분의 제네릭의 타입 매개변수는 생략 가능하다. 
MyClass<String, Integer> a = new MyClass<>();

❓제네릭 사용 이유와 이점

1️⃣ 컴파일 타임에 타입 검사를 통해 예외 방지

제네릭은 자바 1.5에 추가된 스펙이다. 

JDK 1.5 이전에는 여러 타입을 다루기 위해 Object 타입을 사용했었다. 그러나 Object 타입을 선언할 경우 반환된 Object 객체를 다시 원하는 타입으로 일일히 타입 변환을 해야 하며, 런타임 에러가 발생할 가능성이 존재했다. 

 

Object 타입 선언 후, 형변환에 실수를 할 경우 ClassCastException 런타임 에러가 발생하게 된다. 그러나 이는 미리 코드에서 알려주지 않는다.

 

제네릭을 사용하면 코드를 실행하기 전 컴파일 타임에 미리 에러를 찾아 알려주기 때문에 실수를 방지할 수 있다.

 

이처럼 제네릭은 클래스나 메서드를 정의할 때 타입 파라미터로 객체의 서브 타입을 지정해줌으로써, 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거하여 개발을 용이하게 해준다. 

 

2️⃣ 불필요한 캐스팅을 없애 성능 향상 

Apple[] arr = { new Apple(), new Apple(), new Apple() };
FruitBox box = new FruitBox(arr);

// 가져온 타입이 Object 타입이기 때문에 일일히 다운캐스팅을 해야함 - 쓸데없는 성능 낭비
Apple apple1 = (Apple) box.getFruit(0);
Apple apple2 = (Apple) box.getFruit(1);
Apple apple3 = (Apple) box.getFruit(2);

Apple 배열을 FruitBox의 Object 배열 객체에 넣고 배열 요소를 가져올 때 반드시 다운 캐스팅을 통해 가져와야 했다. 

이는 곧 추가적인 오버헤드가 발생하는 것과 같다. 

 

// 미리 제네릭 타입 파라미터를 통해 형(type)을 지정해놓았기 때문에 별도의 형변환은 필요없다.
FruitBox<Apple> box = new FruitBox<>(arr);

Apple apple = box.getFruit(0);
Apple apple = box.getFruit(1);
Apple apple = box.getFruit(2);

반면 제네릭은 미리 타입을 지정 & 제한하기 때문에 형 변환(Type Casting)의 번거로움을 줄일 수 있다. 


❗주의할 점

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

static 멤버는 클래스가 동일하게 공유하는 변수로서 제네릭 객체가 생성되기도 전에 이미 자료 타입이 정해져 있어야 하기 때문에 논리적인 오류이다.


⌛제한된 Generic과 와일드 카드

extends, super, 와일드 카드를 이용하여 특정 범위만 허용하고 나머지 타입은 제한하여 제네릭을 사용할 수 있다. 

PECS (Producer Extends, Consumer Super
외부에서 데이터를 생산한다면 extends를, 외부에서 데이터를 소모한다면 super를 사용하라

1️⃣ extends & super

기본적인 extends 용법은 <T extends [제한타입]> 이다.

제한 타입과 제한 타입의 자손 타입만 가능하다는 뜻이다.

class Calculator<T extends Number>{
	void add(T a, T b){}
	void min(T a, T b){}
}
public class Main{
	public static void main(String[] args){
    	// 제네릭에 Number 클래스만 받도록 제한
        Calculator<Number> cal1 = new Calculator<>();
        Calculator<Integer> cal1 = new Calculator<>();
        Calculator<Double> cal1 = new Calculator<>();
        
        // Number 이외의 클래스들은 오류
        Calculator<Object> cal1 = new Calculator<>();
        Calculator<String> cal1 = new Calculator<>();
    }
}
  • 위의 예제에서는 제네릭을 Number 클래스와 그 하위 타입 (Integer, Double) 들만 받도록 타입 파라미터 범위를 제한 한 것이다. 
<K extends t> T와 T 자손 타입만 가능
<? extends T> T와 T 자손 타입만 가능
<? super T> T와 T의 부모(조상) 타입만 가능
<?> 모든 타입 가능 <? extends Object>랑 같은 의미

 

🔎예시 설명 

  • extends T : 상한 경계, 뒤에 오는 타입이 최상위 타입으로 한계가 정해짐
    • < T extends Fruit > : Fruit, Apple 타입만 올 수 있음
    • < T extends Beef > : Beef 타입만 올 수 있음
    • < T extends Food > : Food, Fruit, Apple, Meat, Beef 타입이 올 수 있음
  • super T : 하한 경계, 뒤에 오는 타입이 최하위 타입으로 한계가 정해짐
    • < ? super Fruit > : Fruit, Food 타입만 올 수 있음
    • < ? super Beef > : Beef, Meat, Food 타입만 올 수 있음
    • < ? super Food > : Food 타입만 올 수 있음

2️⃣ 와일드 카드

<?> 는 Unbounded Wildcard라 부르며, 특정 타입에 종속되지 않고, 어떠한 타입이든 올 수 있음을 의미한다. 

여기서 중요한 것은 와일드카드가 any type이 아닌, unknown type이라는 점이다. 그래서 타입이 지정되지 않음을 말한다.

  • <? extends 상위 타입> : Upper Bounded Wildcards (상위 클래스 제한)
    • 타입 파라미터를 대치하는 구체적인 타입으로 상위 타입과 상위 타입의 하위 타입만 올 수 있다. 
  • <? super 하위 타입> : Lower Bounded Wildcards (하위 클래스 제한)
    • 타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 하위 타입의 상위 타입만 올 수 있다. 

참고 :https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%A0%9C%EB%84%A4%EB%A6%ADGenerics-%EA%B0%9C%EB%85%90-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0

https://github.com/devSquad-study/2023-CS-Study

'CS > Java' 카테고리의 다른 글

Collection Framework  (0) 2025.04.28
Reflection  (1) 2025.04.24
Java 8 특징  (0) 2025.04.07
자바의 비동기 처리  (0) 2025.04.07
Error & Exception  (0) 2025.04.04