JAVA

16. Generic

ggomjiu 2025. 4. 29. 02:48

Generic

: 모든 종류의 타입을 다룰 수 있도록, 클래스나 메소드를 일반화된 타입 매개 변수를 이용하여 선언하는 기법

- C++의 템플릿과 유사

 

cf) 템플릿(Template)

: 메소드나 클래스 코드를 찍어내듯이 일반화시키는 도구

 

사용법

- 컬렉션 클래스에서 타입 매개 변수로 사용하는 문자는 다른 변수와 혼동을 피하기 위해 일반적으로 하나의 대문자를 사용

- 관례적으로 타입 매개 변수에 많이 사용하는 문자이며, 반드시 일치할 필요는 없음

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

1. generic 선언

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

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

2) 제네릭 타입 두개

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

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

3) 객체 생성

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

public class Main {
    public static void main(String[] args) {
        MyClass<String, Integer> a = new MyClass<Strnig, Integer>();
    }
}
  • 생성된 제네릭 클래스를 사용해 객체를 생성할 때, 구체적인 타입을 명시해주어야 함
  • T는 String, K는 Integer가 됨
  • 주의해야 할 점 : 파라미터로 명시할 수 있는 것은 참조타입밖에 올 수 없음
    • primitive type(int, doubld, char..)은 올 수 없음
    • Wrapper Class를 사용해야 primitive type을 쓸 수 있음
    • 참조 타입이 올 수 있다는 것은, 사용자가 정의한 클래스도 타입으로 올 수 있음

2. 제네릭 클래스

class MyClass<K, V> { 
    private K first;
    private V second;

    void set(K first, V second) {
        this.first = first;
        this.second = second;
    }

    K getFirst() {
        return first;
    }

    V getSecond() {
        return second;
    }
 }

public class Main {
    public static void main(String[] args) {
        MyClass<String, Integer> a = new MyClass<String, Integer>();
        
        a.set("hi",10);

        System.out.println("first data : " + a.getFirst());
        System.out.println("K Type : " + a.getFirst().getClass().getName());
        System.out.println("second data : " + a.getSecond());
        System.out.println("V Type : " + a.getSecond().getClass().getName());
    }
}
  • 객체를 생성할 때 < > 안에 타입 파라미터를 지정함

3. 제네릭 메소드

public <T> T genericMethod(T o) {
    ...
}

[접근제어자] <제네릭타입> [반환타입] [메소드명] ([제네릭타입] [파라미터]) {
    ...
}
  • 클래스와 달리 반환타입 이전에 < > 제네릭 타입을 선언
  • genericMethod() 는 파라미터 타입에 따라 T 타입이 결정됨
    • 즉, 클래스에서 지정한 제네릭 유형과 별도로 메소드에서 독립적으로 제네릭 유형을 선언하여 쓸 수 있음
  • 위와 같은 방식이 필요한 이유는 정적 메소드로 선언할 때 필요하기 때문
  • 해당 클래스 객체가 인스턴스와 했을 때, < > 안에 파라미터로 넘겨준 타입이 지정된다는 뜻
  • 객체 생성을 통해 접근할 필요 없이 프로그램 실행 시 이미 메모리에 올라가있기 때문에 클래스 이름을 바로 쓸 수 있음

cf) Static vs Generic

▶ Generic
- 제네릭 메소드의 제네릭 타입은 지역변수처럼 사용되기 때문에, 프로그램이 실행되어 static 메소드가 메모리에 올라갈 때 타입 지정 없이 메소드의 틀만 공유될 수 있음
- 이후, 메소드 호출 시 타입을 지정하면 됨
=> 제네릭 메소드는 제네릭 클래스 타입과 별도로 지정됨

 

제한된 Generic과 와일드 카드

1. PECS(Producer Extends, Consumer Super)

: 외분에서 데이터를 생산한다면 extends를, 외부에서 데이터를 소모한다면 super를 사용

<K extends T>	// T와 T의 자손 타입만 가능 (K는 들어오는 타입으로 지정 됨)
<K super T>	// <T super [타입]> 은 존재하지않음
 
<? 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 타입 > 
    • 매개변수의 자료형을 특정 클래스를 상속받은 클래스로만 제한함
  • < ? super 타입 >
    • 매개변수의 자료형을 특정 클래스와 그 클래스의 상위 클래스로만 제한함
  • < T extends 타입 >
    • 상속을 이용해서 T의 자료형을 제한함
    • 클래스 선언 시 사용하며, 인스턴스 생성 시 특정 클래스를 상속받은 클래스형만 인스턴스 내부에서 사용할 수 있도록 함
    • 특정 인터페이스를 구현한 클래스만 사용하려는 경우에도 사용 가능

cf) < T super 타입 >

- EX) < T super HashMap >

- Type Erasure를 통해 Object로 변환되기 때문에, 어떤 타입인지 추론되지 않는 T는 결국 Object와 다르지 않음

- 만일 타입 정보 소거가 이루어지지 않는다고 해도, 타입 파라미터는 클래스와 인터페이스를 가지리 않으므로, T가 어떤 타입이 되는지 모호해지는 문제가 발생

- < T super HashMap > : 계층구조를 올라가면 T는 AbstractMap, Map, Object가 모두 올 수 있게 됨

-> 이러한 경우 T를 특정할 수 없게 되니 쓸모가 없는 코드가 됨

 

cf) Generic - Type Erasure

: 자바 코드를 컴파일할 때, 타입을 검사하고, 런타임 시 해당 타입을 삭제하는 절자 컴파일 시 안정성을 보장받을 수 있음

 

Generic 장점

  1. 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있음
  2. 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없음 -> 관리가 편함
  3. 비슷한 기능을 지원하는 경우 -> 코드 재사용성이 높아짐

 

'JAVA' 카테고리의 다른 글

18. Collection Framework(List, Map, Set...)  (0) 2025.04.29
17. Reflection(Annotation)  (0) 2025.04.29
15. Default Method & 추상 클래스 & 인터페이스  (0) 2025.04.29
14. Lambda & Functional Interface  (0) 2025.04.17
13. 자바 8의 특징  (1) 2025.04.17