이것이 자바다 - Part 13

mj·2023년 1월 24일
0
post-thumbnail

Part 13 제네릭

제네릭이란?

다음과 같이 Box 클래스를 선언하려고 한다. Box 에 넣을 내용물로 content 필드를 선언하려고 할 때 필드의 타입을 Object 로 설정하면 모든 객체를 대입할 수 있지만, 어떤 타입의 객체가 들어있는지 특정지을 수 없다.

따라서 Object 타입으로 content 필드를 선언하는 것은 좋은 방법이 아니다.

Box 생성하기 전에 우리는 어떤 내용물을 넣을지 이미 알고 있다. 따라서 Box 를 생성할 때 저장할 내용물의 타입을 미리 알려주면 Box는 content 에 무엇이 대입되고, 읽을 때 어떤 타입으로 제공할지를 알게 된다.

이것이 제네릭이다.

제네릭

  • 제네릭이란 결정되지 않은 타입을 파라미터로 처리하고, 실제 사용할 때 파라미터를 구체적인 타입으로 대체시키는 기능

다음은 Box 클래스에서 결정되지 않은 content 의 타입을 T 라는 타입 파라미터로 정의한 것이다.

public class Box<T> {
	public T content;
}

<T> 는 T가 타입 파라미터임을 뜻하는 기호로, 타입이 필요한 자리에 T를 사용할 수 있음을 알려주는 역할을 한다

여기에서 Box 클래스는 T를 content 필드의 타입으로 사용하였다.
즉, Box 클래스는 T가 무엇인지 모르지만, Box 객체가 생성될 시점에 다른 타입으로 대체된다는 것을 알고 있다.

만약 Box 의 내용물로 String을 저장하고 싶다면 다음과 같이 Box 를 생성할 때 타입 파라미터 T대신 String으로 대체하면 된다.

Box<String> box = new Box<String>();
box.content = "안녕하세요";
String content = box.content;

타입 파라미터를 대체하는 타입은 클래스 및 인터페이스이기 때문에 int 나 float 같은 기본 타입은 사용할 수 없다.

제네릭 타입

제네릭 타입은 결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
제네릭 타입은 선언부에 '<>' 부호가 붙고 그 사이에 타입 파라미터들이 위치한다.

public class 클래스명<A, B, ...> { ... }
public interface 인터페이스명<A, B, ...> { ... }

타입 파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만 일반적ㄹ으로 대문자 알파벳 한 글자로 표현한다.

외부에서 제네릭 타입을 사용하려면 타입 파라미터에 구체적인 타입을 지정해야 한다.

만약 지정하지 않으면 Object 타입이 암묵적으로 사용된다.

제네릭 메소드

제네릭 메소드는 타입 파라미터를 가지고 있는 메소드를 말한다.

타입 파라미터가 메소드 선언부에 정의된다는 점에서 제네릭 타입과 차이가 있다.

제네릭 메소드는 리턴 타입 앞에 <> 기호를 추가하고 타입 파라미터를 정의한 뒤, 리턴 타입과 매개변수 타입에서 사용한다.

public <A, B, ...> 리턴타입 메소드명(매개변수, ...) { /// }

다음 boxing() 메소드는 타입 파라미터로 <T>를 정의하고 매개변수 타입과 리턴 타입에서 T를 사용한다.

public <T> Box<T> boxing(T t) { ... }

타입 파라미터 T는 매개값이 어떤 타입이냐에 따라 컴파일 과정에서 구체적인 타입으로 대체된다.

Box<Intger> box1 = boxing(100);
Box<String> box2 = boxing("안녕하세요");

제한된 타입 파라미터

경우에 따라서는 타입 파라미터를 대체하는 구체적인 타입을 제한할 필요가 있다.
예를 들어 숫자를 연산하는 제네릭 메소드는 대체 타입으로 Number 또는 자식 클래스(Byte, Short, Integer, Long, Double)로 제한할 필요가 있다.

특정 타입과 자식 또는 구현 관계에 있는 타입만 대체할 수 있는 타입 파라미터를 제한된 타입 파라미터라고 한다.

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

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

public <T extends Number> boolean compare(T t1, T2) {
	double v1 = t1.doubleValue();
    double v2 = t2.doubleValue();
    return (v1 == v2);

타입 파라미터가 Number 타입으로 제한되면서 Object의 메소드뿐만 아니라 Number 가 가지고 있는 메소드도 사용할 수 있다.

와일드카드 타입 파라미터

제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터로 ?(와일드카드) 를 사용할 수 있다.
? 는 범위에 있는 모든 타입으로 대체할 수 있다는 표시이다.

타입 파라미터의 대체 타입으로 Student와 자식 클래스인 HighStudent 와 MiddleStudent만 가능하도록 매개변수를 다음과 같이 선언할 수 있다.

리턴타입 메소드명(제네릭타입<? extends Student> 변수) { ... }

반대로 Worker와 부모 클래스인 Person 만 간으하도록 매개변수를 다음과 같이 선언할 수 있다.

리턴타입 메소드명(제네릭타입<? super Worker> 변수) { ... }

어떤 타입이든 가능하도록 매개변수를 선언할 수도 있다.

리턴타입 메소드명(제네릭타입<? 변수) { ... }

문제

  1. 제네릭에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 컴파일 시 강한 타입 체크를 할 수 있다.
    ➋ 타입 변환(casting)을 제거한다.
    ➌ 제네릭 타입은 타입 파라미터를 가지는 제네릭 클래스와 인터페이스를 말한다.
    ➍ 제네릭 메소드는 리턴 타입으로 타입 파라미터를 가질 수 없다.
  • 답 : ➍
  1. ContainerExample 클래스의 main() 메소드는 Container 제네릭 타입을 사용하고 있습니다.
    main() 메소드에서 사용하는 방법을 참고해서 Container 제네릭 타입을 선언해보세요.
public class ContainerExample {
	public static void main(String[] args) {
 		Container<String> container1 = new Container<String>();
 		container1.set("홍길동");
 		String str = container1.get();
 		Container<Integer> container2 = new Container<Integer>();
 		container2.set(6);
 		int value = container2.get();
	}
}
  • 답 :
public class Container<T> {
	private T t;
	public T get() {
 		return t;
	}
	public void set(T t) {
 		this.t = t;
	}
}
  1. ContainerExample 클래스의 main() 메소드는 Container 제네릭 타입을 사용하고 있습니다.
    main() 메소드에서 사용하는 방법을 참고해서 Container 제네릭 타입을 선언해보세요.
public class ContainerExample {
	public static void main(String[] args) {
 		Container<String, String> container1 = new Container<String, String>();
 		container1.set("홍길동", "도적");
 		String name1 = container1.getKey();
 		String job = container1.getValue();
 		Container<String, Integer> container2 = new Container<String, Integer>();
 		container2.set("홍길동", 35);
 		String name2 = container2.getKey(); 
        int age = container2.getValue();
	}
}
  • 답 :
public class Container<K, V> {
	private K key;
	private V value;
	public K getKey() {
 		return this.key;
	}
	public V getValue() {
 		return this.value;
	}
	public void set(K key, V value) {
 		this.key = key;
 		this.value = value;
	}
}
  1. 다음 Util 클래스의 정적 getValue() 메소드는 첫 번째 매개값으로 Pair 타입과 하위 타입만 받고, 두 번째 매개값으로 키값을 받습니다. 리턴값은 키값이 일치할 경우 Pair에 저장된 값을 리턴하고, 일치하지 않으면 null을 리턴하도록 Util 클래스와 getValue() 제네릭 메소드를 작성해보세요.
public class UtilExample {
	public static void main(String[] args) {
		Pair<String, Integer> pair = new Pair< >( "홍길동" , 35 );
 		Integer age = Util.getValue(pair, "홍길동" );
 		System.out.println(age);

 		ChildPair<String, Integer> childPair = new ChildPair< >( "홍삼원" , 20 );
 		Integer childAge = Util.getValue(childPair, "홍삼순" );
 		System.out.println(childAge);

 		/*OtherPair<String, Integer> otherPair = new OtherPair< >("홍삼원", 20);
 		//OtherPair는 Pair를 상속하지 않으므로 컴파일 에러가 발생
 		int otherAge = Util.getValue(otherPair, "홍삼원");
 		System.out.println(otherAge);*/
	}
}
public class Pair<K, V> {
	private K key;
	private V value;
	public Pair(K key, V value) {
 		this.key = key;
 		this.value = value;
	}
	public K getKey() { return key; }
	public V getValue() { return value; }
}

public class ChildPair<K, V> extends Pair<K,V> {
	public ChildPair(K k, V v) {
 		super(k, v);
	}
}
public class OtherPair<K, V> {
	private K key;
	private V value;
	public OtherPair(K key, V value) {
 		this.key = key;
 		this.value = value;
	}
	public K getKey() { return key; }
	public V getValue() { return value; }
}
  • 답 :
public class Util {
	public static <K, V> V getValue(Pair<K, V> p, K k) {
 		if(p.getKey() == k) {
 			return p.getValue();
 		} else {
 			return null;
 		}
	}
}
profile
사는게 쉽지가 않네요

0개의 댓글