Java

SCJP 70번 문제 와일드 카드와 제네릭에 대한 간단한 기술

bang2001 2013. 7. 25. 09:21

QUESTION 70

Given:
11. // insert code here
12. private N min, max;
13. public N getMin() { return min; }
14. public N getMax() { return max; }
15. public void add(N added) {
16. if (min == null || added.doubleValue() < min.doubleValue())
17. min = added;
18. if (max == null || added.doubleValue() > max.doubleValue())
19. max = added;
20. }
21. }

Which two, inserted at line 11, will allow the code to compile? (Choose two.)

A. public class MinMax<?> {
B. public class MinMax<? extends Number> {
C. public class MinMax<N extends Object> {
D. public class MinMax<N extends Number> {
E. public class MinMax<? extends Object> {
F. public class MinMax<N extends Integer> {

Answer: DF

-------------------------------------------------------------------------------------------

먼저 문제에 대하여 해석해보겠습니다.

"다음 코드에서 컴파일을 완료하기 위해서 11번째 라인에 들어갈 코드는 어떤것인가요? 두개를 고르세요" 라는 문제입니다.

이 문제는 와일드카드에 대한 문제입니다.
저희가 수업시간에 배운 제네릭 기억하고 계시죠? 이 제네릭과 관련된 부분입니다.
먼저 제네릭에 대해서 약간 설명하고 와일드카드에 대해서 설명하겠습니다.

제네릭이란 저희가 수업시간에 백터를 배울때 처럼 객체를 자료형처럼 사용할 때 
프로그래머가 원하는 특정객체만 받을수 있게 하는것이 제네릭입니다.
그래서 제네릭이 구현되어있는 백터클래스를 보게되면

public class Vector<E> // <- 이처럼 상징적인 문자 E를 활용하여 제네릭을 지정하였습니다.
{
public boolean add(E e) // <- 이와같이 클래스를 선언할때 제네릭을 지정하였고, 
                                                   후에 클래스를 사용할 때 제네릭 타입을 지정해주면
{          해당 제네릭 타입을 통해서 add()라는 메소드의 매개
                                                   변수 타입이 제네릭 타입으로 치환됩니다.
//insert code...
}
}

이처럼 클래스를 선언할 때 제네릭을 이용하여 상징적인 문자 E를 활용하여 이 클래스를 다른곳에서 사용하고자 할때 제네릭을 선언해야 합니다.
만약 클래스 선언시 제네릭을 사용하겠다고 명시해놓고, 다른곳에서 이 클래스의 객체를 생성하여 활용할 때 제네릭 타입을 명시하지 않게되면
다음과 같은 메시지가 뜨게 됩니다.

ex) Integer s1 = new Integer(123);
Vector v = new Vector(); <--------------제네릭 타입을 명시하지 않음
v.add(s1);

---------- javac ----------
Note: WrappedString.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

출력 완료 (3초 경과) - 정상 종료


따라서 제네릭을 구현한 클래스의 객체를 생성시에 <> 안에 객체자료형(제네릭 타입)을 명시해야 합니다.
<> 안에 객체자료형을 명시하게 되면 add()라는 메소드에서 객체를 매개변수를 발도록 되어있는데 이 객체에 들어갈 객체의 자료형을 명시하게 됩니다.
따라서 사용자가 정의한 객체자료형 외에는 다른 객체가 들어갈 수 없도록 하는 것 입니다.

이처럼 제네릭을 사용하여 프로그래머가 지정한 객체 이외에 다른 객체의 난입을 방지할 수 있습니다.
하지만 저희가 배운 제네릭에서는 모든 객체를 받을 수 있게끔 하거나
(물론 제네릭안에 Object타입을 명시하면 가능합니다. 하지만 이렇게 하게되면 내부적으로 자식객체에서 상위객체로 형변환이 이루어지게 됩니다.)
혹은 특정객체의 상속관계에 있는 객체들을 제네릭으로 지정하고자 할때에는 제약이 따름니다.

이때 사용하는것이 바로 와일드 카드입니다. 교과서 P.344에 나와있는 내용입니다.

<?> : 모든 객체를 의미한다(Object와 같다고 보시면 됩니다.)
<? super 객체자료형> : 명시된 객체자료형을 포함한 상위객체들을 의미한다.
<? extends 객체자료형> : 명시된 객체자료형을 포함한 하위객체들을 의미한다.

이렇게 와일드 카드를 사용할 수 있습니다. 와일드 카드는 다음과 같이 사용할 수 있습니다.
먼저 SCJP 문제에 나온것처럼 처음에 클래스 선언시에 와일드 카드를 사용하여 클래스를 사용시
제네릭 타입의 설정에 제한을 두는것이 있고, 또하나는 일반적인 제네릭이 구현된 클래스에 
제네릭 타입설정시 와일드 카드를 사용하는 방법이 있습니다.

전자와 후자에 대한 예를 들어보겠습니다.

public class MinMax<N extends Number> /* 클래스 선언시 와일드 카드 사용 SCJP문제에 나와있는 코드이다.*/
{
private N min, max;

public N getMin()
return min; 
}

public N getMax() 
return max; 
}

public void add(N added) 
{
if (min == null || added.doubleValue() < min.doubleValue()) min = added;
if (max == null || added.doubleValue() > max.doubleValue()) max = added;
}

public static void main(String args[])
{
MinMax<Integer> ins = new MinMax<Integer>(); //Number
ins.add(new Integer(10));
ins.add(new Integer(50));

Integer min = ins.getMin();
Integer max = ins.getMax();

System.out.println("min : "+min+", max : "+max);

// "min : 10, max : 50" 이 출력이 된다.

//-----------------------------------------------------------//

MinMax<String> ins2 = new MinMax<String>();
ins2.add(new String("String객체 01"));
ins2.add(new String("String객체 02"));
//다음 제네릭을 선언하면 에러가 걸린다. 와일드 카드를 사용하였기 때문에 Number객체
                   를 포함한 Number객체를 상속받는
//객체만 제네릭으로 지정할 수 있다.
// 에러 메시지 

/*---------- javac ----------
MinMax.java:34: type parameter java.lang.String is not within its bound
MinMax<String> ins2 = new MinMax<String>();
   ^
MinMax.java:34: type parameter java.lang.String is not within its bound
MinMax<String> ins2 = new MinMax<String>();
 ^
2 errors

출력 완료 (5초 경과) - 정상 종료 */
}
}

Vector<? super SubClass> v3 = new Vector<SuperClass>(); /* 제네릭 타입설정시 와일드 카드 사용 */

또한 와일드 카드를 사용하는데 있어서 다음과 같은 조건을 가집니다.

- 와일드 카드를 사용시 와일드 카드를 적용시킬 클래스에서 내부적으로 생성자를 통한 객체의 전달이 구현되어야 한다.

이 부분이 구현되지 않으면 제네릭타입을 지정해주어도 제네릭이 구현된 클래스 내에서 이 객체자료형을 전달받지 못하기 때문입니다.
따라서 생성자로 구현이 되어있지 않은.. 위에서 잠시 예제로 사용한 Vector로 다시 예를 들어보겠습니다.

Vector<? super SubClass> v3 = new Vector<SuperClass>(); /* 제네릭 타입설정시 와일드 카드 사용 */
SubClass sub = new SubClass();
SuperClass super_01 = new SuperClass();
v3.add(sub_Instance);
v3.add(super_01);

/*---------- javac ----------
SubClass.java:37: cannot find symbol
symbol  : method add(SuperClass)
location: class java.util.Vector<capture#684 of ?>
v3.add(super_01);
  ^
1 error

출력 완료 (3초 경과) - 정상 종료*/

위와 같은 에러가 발생합니다. SubClass로 제네릭 타입이 설정이 되고,
와일드카드로써 제네릭 타입이 설정이 되지 않았기 때문에 
v3.add(sub_Instance); <- 이 부분에서는 정상적으로 수행하게 되고,
v3.add(super_01); <- 이 부분에서는 이미 add()라는 메소드의 매개변수가 SubClass타입의 객체자료형으로
설정되어있기 때문에 그 외에 다른 객체자료형으로 되어있는.. 즉 오버로드 되어있는 메소드가 없기 때문에
"cannot find symbol" 라는 메시지가 뜨게 됩니다. 이처럼 와일드카드로 설정한 제네릭타입을 제대로 전달받지 못한것을
볼 수 있습니다.

이미 여기서 SCJP 70번에 대한 답이 나왔는데요. 문제에서 와일드 카드 사용시 어떤 수의 비교가 이루어지기 때문에
Number를 상속받은 객체가 와야하고(Integer나 Float등 수와 관련된 객체는 모두 Number객체를 상속받고 있습니다.)

때문에 extends Number가 와야 하고, 혹은 다소 제한적이라 하더라도 Integer가 올 수 있습니다.
그리고 이 문제의 코드에서 상징적인 문자 N을 이용하여 객체의 타입을 지정하도록 하였기 때문에
객체자료형이 잘 전달이 될려면 같은 문자를 이용해야 합니다. 따라서

- public class MinMax<N extends Number> {
- public class MinMax<N extends Integer> {

이 되야 합니다. 따라서 답은 D번과 F번입니다.