Java

Generic(제네릭) 이란? + 와일드카드, generic 예시

woo0doo 2023. 2. 16. 00:39

Generic

 

 

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

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

 

 

JAVA에서 제네릭이란?

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

 

 

 

제네릭을 왜 사용할까?

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

 

제네릭을 사용하면 실수로 지정한 타입이 들어오는 경우 컴파일 시점에서 미리 예방할 수 있게 된다. 또한 클래스 외부에서 데이터 타입을 지정하기 때문에, 타입을 고려해서 이리저리 변환할 필요가 없다. 따라서 코드의 재사용성이 높아지고 전체 코드 관리가 용이해진다.

 

List v = new ArrayList();
v.add("test"); // A String that cannot be cast to an Integer
Integer i = (Integer)v.get(0); // Run time error

위와 같은 코드는 런타임 에러를 발생한다. List에 String을 저장하고 Integer로 타입 변환하는 오류가 런타임에 잡히는 것이다.

 

List<String> v = new ArrayList<String>();
v.add("test");
Integer i = (Integer)v.get(0); // (type error)  compilation-time error

하지만 위와 같이 제네릭을 이용하면 컴파일 에러를 발생시켜 컴파일 시에 오류를 잡아낼 수 있다. 런타임 에러보다 컴파일 에러가 훨씬 디버깅이 쉽기 때문에 이점이 많다.

 

또한 제네릭에서는 <>안에서 데이터 타입을 자유롭게 설정할 수 있다. 특정 자료구조를 만들 때 Integer, String, Boolean 등 데이터 타입에 따라 다른 자료구조를 만든다면 매우 비효율적이다. 제네릭은 이러한 상황일 때 굉장히 효율적이다.

 

ArrayList list = new ArrayList(); //제네릭을 사용하지 않을경우
list.add("test");
String temp = (String) list.get(0); //타입변환이 필요함
        
ArrayList<String> list2 = new ArrayList(); //제네릭을 사용할 경우
list2.add("test");
temp = list2.get(0); //타입변환이 필요없음

 

제네릭에서 많이 사용되는 문자열들

이 문자열들은 개발자들이 암묵적으로 사용하는 코드일 뿐이지 사용자가 임의로 지정한 문자열을 넣어도 제네릭이 동작하는데에 아무런 영향을 미치지 않는다.

 


예시

 

 

제네릭 클래스

class ExampleGeneric<T> {
    private T t;

    public void setT(T t) {
        this.t = t;
    }
			
    public T getT() {
        return t;
    }
}

클래스를 설계할 때 구체적인 타입을 명시하지 않고 타입 파라미터로 넣어두었다가 실제 설계한 클래스가 사용될 때 ExampleGeneric<String> YD = New ExampleGeneric<>(); 이런식으로 구체적인 타입을 지정하면서 사용하면 타입 변환을 최소화 할 수 있다.

 


제네릭 인터페이스

 

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

class ExGeneric implements ExampleGeneric<String> {

// ExampleGeneric<String> <- String으로 넘어왔기 때문에 example()의 반환값이 String이여야만 한다.

    @Override
    public String example() {
        return "YD";
    }
}

 

 


멀티 타입 파라미터 사용

 

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;
    }
}

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

 


제네릭 메소드

 

선언

// 제네릭 메소드 선언
// 매개 변수 타입: T
// 리턴 타입: Box<T>
public <T> Box<T> boxing(T t) {
    Box<T> box = new Box<T>();
    box.set(t);
    return box;
}

 

 

먼저 리턴 타입(int, boolean, String, Box<T> 등등) 앞에 <T>와 같이 타입 파라미터를 기술한 다음, 

매개 변수에 제네릭 타입 파라미터를 기입하면 된다.

 

 

호출

 

public class BoxingMethodExample {
	public static void main(String[] args) {
		Box<Integer> box1 = <Integer>boxing(100);
		int intValue = box1.get();
		
		Box<String> box2 = boxing("YD");
		String strValue = box2.get();
	}
}

 

메서드를 호출 할 때, <Integer> boxing(100); 처럼 타입 파라미터를 구체적으로 명시하여도 되고, 단순히 box(100);으로 작성하여도 타입 파라미터를 Integer로 추정하여 대입하게 된다.

 

 

제한된 타입 파라미터

// extends를 이용해 타입 제한
public static <T extends Number> int compare(T t1, T t2) {
		double v1 = t1.doubleValue(); 
		double v2 = t2.doubleValue();

		return Double.compare(v1, v2);
}

위 제네릭 메소드에서 int 앞에 <T extends Number>라는 부분이 의미하는 바는

Number 클래스 또는 Number 클래스의 하위 타입 클래스만 사용할 수 있음을 의미한다.

그러므로 이 compare 제네릭 메소드에 String이나 기타 다른 타입이 삽입되는 것을 제한 할 수 있게 된다.

 

주의할 점

 

1. 상위 타입은 클래스뿐만 아니라 인터페이스도 가능하다. 하지만 그렇다고 extends를 implements라고 쓰진 않는다.

2. 제네릭 메소드의 중괄호 {} 안에서 타입 파라미터 변수로 가능한 것은 상위 타입의 멤버(필드, 메소드)로 제한한다.

- 즉 <T extends Number>라고 제네릭 메소드의 타입을 제한했다면, 메소드의 실행 부분에서 Number 타입의 필드와 메소드만 사용할 수 있다는 뜻이다. Integer에는 있고 Number에는 없는 필드나 메소드는 사용할 수 없다.

 

 

 


 

제네릭 와일드 카드

 

와일드카드는 물음표 ?로 표시하며 Java에서 unknown type이다. 와일드카드는 매개변수, 필드 또는 지역 변수의 유형 때론 반환 유형으로 다양한 상황에서 사용할 수 있다.

 

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

 

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

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

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

 

 

 

 

 


출처

 

https://coding-factory.tistory.com/573

https://hahahoho5915.tistory.com/69

https://jehuipark.github.io/java/java-generic