본문 바로가기

Java

자바에서의 제네릭과 와일드카드

[자바에서 제네릭이란]

  • 제네릭은 매개변수화된 유형을 의미한다.
  • 이것은 Integer, String, ...등 및 사용자 정의 유형의 메서드, 클래스 및 인터페이스에 대한 매개변수가 될 수 있도록 하는 것이다.
  • 제네릭을 이용하면 다양한 데이터 유형을 작동하는 클래스를 생성할 수 있다.

 

[왜 제네릭을 사용하는가]

제네릭은 Java 5부터 새로 추가되어 등장했는데, 제네릭 타입을 이용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있게 되었다.

List list = new LinkedList();
list.add(new Integer(1));
Integer i = list.iterator().next();

위와 같이 코드를 구현하게 되면 컴파일러는 어떤 데이터 유형이 반환되었는지 모르기 때문에 아래에 작성된 코드처럼 명시적 형변환이 필요하다. 👉 제네릭을 통해 타입 변환(casting)을 제거한다.

Integer i = (Integer)list.iterator().next();

 

개발자가 어떠한 타입을 사용하려는지 의도를 표현할 수 있고 컴파일러는 타입에 대한 정확성을 보장한다면 훨씬 쉽다는 것이 제네릭의 핵심 아이디어이다.

List<Integer> list = new LinkedList<>();

타입을 포함하는 다이아몬드 연산자<>를 추가하여 이 List의 전문화 범위를 특정 타입으로 좁혀준다.

컬렉션 내부에 타입을 지정하게 되는 것이다.

컴파일러는 컴파일 시, 타입을 적용할 수 있다. 👉 컴파일 시 강한 타입 체크를 할 수 있다.

 

[제네릭 타입]

제네릭 타입은 타입을 파라미터로 갖는 클래스와 인터페이스를 말한다.

제네릭 타입은 클래스 또는 인터페이스 이름 뒤에 다이아몬드 연산자<>가 붙고, 사이에 타입 파라미터가 위치한다.

 

A Simple Box Class

public class Box {
	private Object object;
    
    public void set(Object object) {
    	this.object = object;
    }
    
    pubilc Object get() {
    	return object;
    }
}

 

A Generic Version of the Box Class

제네릭 클래스는 class name<T1, T2, ..., Tn> { /* ... */ }와 같은 형식으로 정의 된다.

public class Box<T> {
	private T t;

	public void set(T t) {
		this.t = t;
	}

	public T get() {
		return t;
	}
}

 

[제네릭 메서드]

제네릭 메서드는 리턴 타입 앞에 다이아몬드 연산자<>가 있다.

제네릭 메서드는 고유한 형식 매개변수를 도입하는 메서드이며 파라미터 타입의 범위를 선언된 메서드로 제한하게 된다.

public <T> List<T> fromArrayToList(T[] a) {
	return Arrays.stream(a)
		.collect(Collectors.toList());
}

 

제한된 타입 파라미터

타입 파라미터에 지정되는 구체적인 타입을 제한할 필요가 종종 있다.

예를 들어 숫자를 연산하는 제네릭 메서드는 매개값으로 Number 타입 또는 하위 클래스 타입(Byte, Short, Integer, Long, Double)의 인스턴스만 가져야한다.

제한된 타입 파라미터를 선언하려면 타입 파라미터 뒤에 extends 키워드를 붙이고 상위 타입을 명시하면 된다.

상위 타입은 클래스뿐만 아니라 인터페이스도 가능하다.

인터페이스라고 해서 implements를 사용하지 않는다.

public <T extends 상위타입> 리턴타입 메소드(매개변수, ...) { ... }
public class Box<T> {
	private T t;

	public void set(T t) {
		this.t = t;
	}

	public T get() {
		return t;
	}

	public <U extends Number> void inspect(U u) {
		System.out.println("T: " + t.getClass().getName());
		System.out.println("U: " + u.getClass().getName());
	}

	public static void main(String[] args) {
		Box<Integer> integerBox = new Box<Integer>();
		integerBox.set(new Integer(10));
		integerBox.inspect("some text");    // error: this is still String!
	}
}

이 처럼 제한된 타입의 매개변수를 포함하도록 제네릭 메서드를 수정하면 inspect 호출 시, String이 포함되어 컴파일에 실패하는 것을 알 수 있다.

 

[와일드 카드의 사용]

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

 

상한 와일드카드(Upper Bounded Wildcards)

제네릭타입<? extends 상위타입>

Integer, Double 그리고 Float과 같이 Number의 리스토와Number의 하위 타입으로 동작하는 메서드를 작성하려면 List<? extends Number>로 확장해야 한다.

List<Number>는 Number 타입의 목록과만 일치하지만 List<? extends Number> 타입은 그 하위 목록과 일치하기 때문에 Number를 확장 하는 것이다.

public static double sumOfList(List<? extends Number> list) {
	double s = 0.0;
	for(Number n : list) {
		s += n.doubleValue();
	}
	return s;
}

sumOfList() 메서드는 list에 있는 숫자의 합계를 반환하게 된다.

List<Integer? li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));

List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));

Integer와 Double는 Number의 하위 타입으로 Integer 개체 list를 통해 계산한 6.0을 출력하고 Double list 또한 Integer와 동일하게 sumOfList() 메서드를 이용하여 7.0 이라는 결과 값을 출력 할 수 있다.

 

무제한 와일드카드(Unbounded Wildcards)

제네릭타입<?>

무제한 와일드카드 유형은 와일드카드 문자(?)를 사용하여 지정된다. 예를들어 List<?>로 나타낼수 있으며 unknown type의 list라고 한다.

  • 무제한 와일드카드를 유용하게 사용할 수 있는 경우
    • Object 클래스에서 제공하는 기능을 이용하여 사용할 수 있는 메서드를 작성할 때
    • 코드가 type parameter에 의존하지 않는 제네릭 클래스의 메서드를 사용하는 경우
public static void printList(List<Object> list) {
	for(Object elem : list) {
		System.out.print(elem + " ");
	}
	System.out.println();
}

List<Integer>, List<vString>, List<Double>등은 List<Object>의 하위 타입이 아니기 때문에 출력을 할 수가 없다.

class unboundedwildcardemo {
	public static void main(String[] args) {
		List<Integer> list1 = Arrays.asList(1, 2, 3);
		List<String> list2 = Arrays.asList("one", "two", "three");

		printlist(list1);
		printlist(list2);
	}

	private static void printList(List<?> list) {
		for(Object elem: list) {
			System.out.print(elem + " ");
 		}
		System.out.println();
	}
}

와일드카드를 사용하게 되면 구체적 타입인 List<Integer>, List<Double>, List<String>등 List<?>의 하위 타입이게 되므로 printList() 메서드를 통해 모든 타입의 list들을 출력할 수 있다.

 

하한 와일드카드(Lower Bounded Wildcards)

상한 와일드카드와 유사한 방식으로 하한 와일드 카드는 unknown type을 특정 타입 또는 해당 타입의 슈퍼 타입으로 제한하게 된다. 하한 와일드카드는 와일드카드 문자(’?’)를 사용하여 표현되며, 그 뒤에 super 키워드가 오고 그 뒤에 하한이 온다. <? super A>

  • 와일드카드의 상한 또는 하한을 지정할 수 있지만 둘 다 지정할 수는 없다.
public static void addNumbers(List<? super Integer> list) {
	for(int i = 1; i <= 10; i++) {
		list.add(i);
	}
}

Integer list와 Integer 상위 타입인 Integer, Number, Object와 같은 타입에서 작동하는 메서드이다. 하지만 Double 타입의 list가 파라미터로 들어오게 된다면 Double은 Integer의 슈퍼 클래스가 아니기 때문에 컴파일 오류가 발생하게 된다.

 

 

 

Reference.

https://www.geeksforgeeks.org/generics-in-java/

https://docs.oracle.com/javase/tutorial/java/generics/index.html

https://www.baeldung.com/java-generics

https://www.geeksforgeeks.org/wildcards-in-java/

https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html