[JAVA] 제네릭(Generic) 개념

강민경·2024년 10월 30일

제네릭(Generic)

자바 제네릭은 generic 타입 및 메소드를 정의하고 사용할 수 있는 언어적 특성입니다.

List<Integer> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();

위와 같이 꺽쇠(<>)안에 클래스 타입이 명시된 패턴을 자주 발견할 수 있습니다.
이것을 제네릭(Generic)이라고 부르며, 제네릭 파라미터는 꺽쇠안에 포함하여 전달합니다.

  • 파라미터 타입이나 리턴 타입에 대한 정의를 위부로 미룬다.
  • 타입에 대해 유연성과 안정성을 확보한다.
  • 런타임 환경에 아무런 영향이 없는 컴파일 시점의 전처리 기술


제네릭 사용 이유

타입을 유연하게 처리하며, 잘못된 타입 사용을 발생할 수 있는 런타임 타입 에러를 컴파일 과정에 검출한다.

자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해 제네릭 코드에 대한 강한 타입 체크를 합니다. 실행시 타입 에러가 나는 것 보다는 컴파일 시에 미리 타입을 강하게 체크해서 에러를 사전에 방지하는 것이 좋습니다. 또한 제네릭 코드를 사용하면 타입을 국한하기 때문에 요소를 찾아올 때 타입 변환을 할 필요가 없어 프로그램 성능이 향상되는 효과를 얻을 수 있습니다.



제네릭 사용법

  • 클래스, 인터페이스 또는 메소드에 선언할 수 있다.
  • 동시에 여러 타입을 선언할 수 있다.
  • 와일드 카드를 이용하여 타입에 대하여 유연할 처리를 가능하게 한다.
  • 제네릭 선언 및 정의 시에 타입의 상속 관계를 지정할 수 있다.

제네릭 타입은 타입을 파라미터로 가지는 클래스와 인터페이스를 말합니다.
제네릭 타입은 클래스 또는 인터페이스 이름 뒤에 <> 부호가 붙고 사이에 타입 파라미터가 위치합니다.

public class MyClass<T>
public interface MyInterface<T>

타입 파라미터는 정해진 규칙은 없지만 일반적으로 대문자 알파벳 한 글자로 표현합니다.

자주 사용하는 타입인자

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


예제

<T>T는 제네릭에서 사용되는 방식에 따라 의미가 다릅니다.

  1. <T> :
    • 타입 파라미터 선언입니다. 제네릭 메소드나 클래스에서 사용할 타입을 선언할 때 <T>처럼 꺽쇠 괄호 안에 넣어 작성합니다. 이를 통해 T라는 이름의 타입 파라미터를 정의하고, 이후 메소드나 클래스에서 T를 사용할 수 있게 됩니다.
    public <T> T method(T parameter) {
    	return parameter;
    }
    이 경우 <T>는 타입 파라미터 선언이고, method 메소드 안에서 T를 타입처럼 사용할 수 있습니다.
  1. T :
    • 선언된 타입 파라미터의 실제 사용입니다. 한 번 <T>로 타입 파라미터를 선언한 후에는 메소드나 클래스 내에서 T를 타입으로 사용하게 됩니다. 이때의 T는 특정한 타입을 의미하는 것이 아니고, 호출 시점에 지정된 타입을 나타냅니다.
    pulbic <T> T method(T parameter) {
    	// 여기서 T는 실제 타입처럼 사용됩니다.
        return parameter;
    }
    호출 시점에 TString으로 지정되면 method의 반환 타입과 매개변수 타입도 String이 됩니다.


제네릭 클래스

class ExClassGeneric<T> {
	private T t;
    
    public void setT(T t) {
    	this.t = t;
    }
    
    public T getT() {
    	return t;
    }
}

위와 같이 클래스를 설계할 때 구체적인 타입을 명시하지 않고 타입 파라미터를 넣어두었다가 실제 설계한 클래스가 사용되어질 때 ExClassGeneric exGeneric = new ExClassGeneric<>(); 이런 식으로 구체적인 타입을 지정하면서 사용하면 타입 변환을 최소화 시킬 수 있습니다.



제네릭 인터페이스

interface ExInterfaceGeneric<T> {
	T example();
}

class ExGeneric implements ExInterfaceGeneric<String> {
	@Override
    public String example() {
    	return null;
    }
}

인터페이스도 위와 같이 클래스처럼 제네릭을 설정해두고 활용할 수 있습니다.



멀티 타입 파라미터 사용

class ExMultiTypeGeneric<K, V> implements Map.Entry<K,V>{

    private K key;
    private V value;

    @Override
    public K getKey() {
        return this.key;
    }

    @Override
    public V getValue() {
        return this.value;
    }

    @Override
    public V setValue(V value) {
        this.value = value;
        return value;
    }
}

타입은 두 개 이상의 멀티 타입 파라미터를 사용할 수 있고, 이 경우 각 타입 파라미터를 콤마로 구분합니다.



제네릭 메소드

class People<T,M>{
    private T name;
    private M age;
	
    People(T name, M age){
        this.name = name;
        this.age = age;
    }

    public T getName() {
        return name;
    }
    public void setName(T name) {
        this.name = name;
    }
    public M getAge() {
        return age;
    }
    public void setAge(M age) {
        this.age = age;
    }
	
    //Generic Mothod
    public static<T,V> boolean compare(People<T,V>p1, People<T,V>p2) {
        boolean nameCompare = p1.getName().equals(p2.getName());
        boolean ageCompare =p1.getAge().equals(p2.getAge());
        return nameCompare && ageCompare;
    }
}

public class ExGeneric {
    public static void main(String []args){
        //타입 파라미터 지정
        People<String,Integer> p1 = new People<String,Integer>("Jack",20);
        //타입 파라미터 추정
        People<String,Integer> p2 = new People("Steve",30);
        //GenericMothod 호출
        boolean result = p1.compare(p1,p2);
        System.out.println(result);
    }
}

제네릭 메서드를 정의할 때는 리턴 타입과 관계없이, 해당 메서드가 제네릭 메서드임을 컴파일러에 명확히 알려야 합니다. 이를 위해 반드시 리턴 타입 앞에 제네릭 타입을 명시해야 합니다. 중요한 점은 제네릭 메서드를 제네릭 클래스뿐 아니라 일반 클래스 내에서도 정의할 수 있다는 것입니다. 즉, 클래스에 지정된 타입 파라미터와 제네릭 메서드에 정의된 타입 파라미터는 서로 독립적입니다. 따라서 제네릭 클래스를 정의할 때 사용한 타입 파라미터와 동일한 이름의 타입 파라미터를 클래스 내부의 제네릭 메서드에서 사용하더라도, 둘은 서로 간섭하지 않고 별개로 동작합니다.



제네릭 와일드카드

public class Calc {
    public void printList(List<?> list) {
       for (Object obj : list) {
    	   System.out.println(obj + " ");  
       }
    }

    public int sum(List<? extends Number> list) {
      int sum = 0;
      for (Number i : list) {
    	  sum += i.doubleValue();  
      }
      return sum;
    }

   public List<? super Integer> addList(List<? super Integer> list) {
      for (int i = 1; i < 5; i++) {
    	 list.add(i); 
      }
      return list;
    }
}

와일드카드 타입에는 총 세가지의 형태가 있으며 물음표(?)라는 키워드로 표현됩니다.

  • 제네릭타입<?>
    - 타입 파라미터를 대치하는 것으로 모든 클래스나 인터페이스 타입이 올 수 있습니다.

  • 제네릭타입<? extends 상위타입>
    - 와일드카드의 범위를 특정 객체의 하위 클래스만 올 수 있습니다.

  • 제네릭타입<? super 하위타입>
    - 와일드카드의 범위를 특정 객체의 상위 클래스만 올 수 있습니다.




참고 사이트

https://hahahoho5915.tistory.com/69

profile
간단한 기록

0개의 댓글