여기서 잠시 다루어 보려는 HashMap 컬렉션 유틸리티 객체도 generic을 이용한 코딩을 할 수 있다.
이와 같이 HashMap 클래스의 정의를 HashMap<K, V>로 하고 있다.
K : Key
V : Value
형태인데 key와 value에 generic을 이용하면 자바 클래스및 사용자 클래스를 줄 수 있다. 하지만 보통은 HashMap을 이용한 간단한 샘플 코드는 Integer 또는 String 을 이용한 코드가 대부분이다. 예를 들면 다음과 같다.
public class HashMap1
{
public static void main( String[] args )
{
HashMap map = new HashMap();
map.add( new Integer( 2 ), "two" );
map.add( new Integer( 4 ), "four" );
System.out.println( map );
System.out.println();
System.out.println( "Enumerate the HashMap" );
Enumeration e = map.elements();
while ( e.hasMoreElements() )
System.out.println( e.nextElement() );
System.out.println();
System.out.println( "Iterate through the HashMap" );
for ( HashMapIterator i = map.begin(); !i.atEnd(); i.advance() )
System.out.println( i.get() + ", key = " + i.key() + ", value = " + i.value() );
System.out.println();
System.out.println( "Demonstrate access" );
System.out.println( "map.get( 2 ) = " + map.get( new Integer( 2 ) ) );
System.out.println( "map.get( 5 ) = " + map.get( new Integer( 5 ) ) );
System.out.println( "map = " + map );
System.out.println();
System.out.println( "Show that duplicates cannot be added." );
Object value = map.add( new Integer( 8 ), "eight" );
if ( value != null )
System.out.println( "Could not add 8." );
else
System.out.println( "Added 8." );
System.out.println( "map = " + map );
value = map.add( new Integer( 4 ), "FOUR" );
if ( value != null )
System.out.println( "Could not add 4." );
else
System.out.println( "Added 4." );
System.out.println( "map = " + map );
System.out.println();
System.out.println( "Demonstrate modification" );
map.put( new Integer( 4 ), "FOUR" );
System.out.println( "map = " + map );
}
}
{
public static void main( String[] args )
{
HashMap map = new HashMap();
map.add( new Integer( 2 ), "two" );
map.add( new Integer( 4 ), "four" );
System.out.println( map );
System.out.println();
System.out.println( "Enumerate the HashMap" );
Enumeration e = map.elements();
while ( e.hasMoreElements() )
System.out.println( e.nextElement() );
System.out.println();
System.out.println( "Iterate through the HashMap" );
for ( HashMapIterator i = map.begin(); !i.atEnd(); i.advance() )
System.out.println( i.get() + ", key = " + i.key() + ", value = " + i.value() );
System.out.println();
System.out.println( "Demonstrate access" );
System.out.println( "map.get( 2 ) = " + map.get( new Integer( 2 ) ) );
System.out.println( "map.get( 5 ) = " + map.get( new Integer( 5 ) ) );
System.out.println( "map = " + map );
System.out.println();
System.out.println( "Show that duplicates cannot be added." );
Object value = map.add( new Integer( 8 ), "eight" );
if ( value != null )
System.out.println( "Could not add 8." );
else
System.out.println( "Added 8." );
System.out.println( "map = " + map );
value = map.add( new Integer( 4 ), "FOUR" );
if ( value != null )
System.out.println( "Could not add 4." );
else
System.out.println( "Added 4." );
System.out.println( "map = " + map );
System.out.println();
System.out.println( "Demonstrate modification" );
map.put( new Integer( 4 ), "FOUR" );
System.out.println( "map = " + map );
}
}
위의 코드가 문제가 된다는 것은 아니다. 다만 HashMap의 Key에 해당하는 클래스가 두 개의 좌표값을 가진 클래스인 경우를 생각해 보자. UserKey라는 클래스는 두 개의 좌표(x, y) 값을 가진 간단한 클래스이다.
/**
* @author Sang-Hyup Lee
*
*/
public class UserKey {
private int x;
private int y;
/**
* @param x
* @param y
*/
public UserKey(int x, int y) {
super();
this.x = x;
this.y = y;
}
/**
* @return the x
*/
public int getX() {
return x;
}
/**
* @param x the x to set
*/
public void setX(int x) {
this.x = x;
}
/**
* @return the y
*/
public int getY() {
return y;
}
/**
* @param y the y to set
*/
public void setY(int y) {
this.y = y;
}
}
다음은 Value 클래스에 해당하는 UserValue 객체를 만들어보자.
import java.util.UUID;
/**
* @author Sang-Hyup Lee
*
*/
public class UserValue {
private int x, y;
private String uniKey;
public UserValue(int x, int y) {
this.x = x;
this.y = y;
this.uniKey = "" + UUID.randomUUID();
}
/**
* @return the uniKey
*/
public String getUniKey() {
return uniKey;
}
/**
* @param uniKey the uniKey to set
*/
public void setUniKey(String uniKey) {
this.uniKey = uniKey;
}
/**
* @return the x
*/
public int getX() {
return x;
}
/**
* @param x the x to set
*/
public void setX(int x) {
this.x = x;
}
/**
* @return the y
*/
public int getY() {
return y;
}
/**
* @param y the y to set
*/
public void setY(int y) {
this.y = y;
}
}
위의 두 개의 key, value 클래스를 이용하여 HashMap 테스트를 위한 JUnit 코드를 만들어보자. 아래의 UnikeyHashMap junit 코드의 결과값을 예상해 보자.
import java.util.HashMap;
import junit.framework.TestCase;
/**
* @author Sang-Hyup Lee
*
*/
public class UnikeyHashMap extends TestCase {
private HashMap<UserKey, UserValue> userHashMap = new HashMap<UserKey, UserValue>();
protected void setUp() throws Exception {
super.setUp();
for ( int x=0; x < 10; x++ ) {
int y = x + 1;
UserKey userKey = new UserKey(x, y);
UserValue sohiPoint = new UserValue(x, y);
userHashMap.put(userKey, sohiPoint);
}
}
public void testUnikeyHashMap() {
UserKey targetUserKey = new UserKey(2, 3);
/*
UserValue userValue = null;
if ( userHashMap.get(targetUserKey) != null ) {
userValue = userHashMap.get(targetUserKey);
}
*/
assertNotNull(userHashMap.get(targetUserKey));
// assertEquals(2, foundSohi.getX());
// assertEquals(3, foundSohi.getY());
}
}
주석문으로 처리된 부분은 UserValue가 정상적으로 찾았을 때를 대비해서 최종 체크하기 위한 코드이다. 적절한 형태로 사용해도 된다.
여기서 중요한 건 testUnikeyHashMap() 테스트가 정상적으로 동작할까? 하는 것이다.
결과는 에러를 리턴한다. 즉, userHashMap.get(targetUserKey)의 결과값이 null 이란 것이다. 일반적인 생각에는 Key를 주어진 Value를 찾아야 한다.
HashMap 클래스의 get 메소드를 따라가보면 문제가 발생한 곳은 파라미터로 받는 key object의 equals 메소드 구현에 있다는 것을 알 수 있다.
이미 이러한 문제는 실무에서 사용할 일이 많았던 거 같다. 즉 HashMap과 같은 Key를 가진 컬렉션 객체에 다중의(Multi) 값을 가진 key 객체를 사용하고 싶은 요구사항인 것이다.
Apache commons collections 오픈소스에 이에 대한 해답이 있다. MultiKey라는 클래스이다.
사용법은 다음과 같이 정의하고 있다.
Example usage:
// populate map with data mapping key+locale to localizedText Map map = new HashMap(); MultiKey multiKey = new MultiKey(key, locale); map.put(multiKey, localizedText); // later retireve the localized text MultiKey multiKey = new MultiKey(key, locale); String localizedText = (String) map.get(multiKey);
간단하다. 실제로 아파치 라이센스를 따르니 해당 소스를 가져와도 상관이 없다. 해당 전체 소스 코드를 살펴볼 필요가 있다.
여기서는 MultiKey 클래스를 이용해서 UnikeyHashMap 테스트 코드를 다음과 같이 수정해 보자.
import java.util.HashMap;
import junit.framework.TestCase;
/**
* @author Sang-Hyup Lee
*
*/
public class UnikeyHashMap extends TestCase {
private HashMap<MultiKey, UserValue> userHashMap = new HashMap<MultiKey, UserValue>();
protected void setUp() throws Exception {
super.setUp();
for ( int x=0; x < 10; x++ ) {
int y = x + 1;
MultiKey userKey = new MultiKey(x, y);
UserValue sohiPoint = new UserValue(x, y);
userHashMap.put(userKey, sohiPoint);
}
}
public void testUnikeyHashMap() {
MultiKey targetUserKey = new MultiKey(2, 3);
UserValue userValue = null;
if ( userHashMap.get(targetUserKey) != null ) {
userValue = userHashMap.get(targetUserKey);
}
assertNotNull(userHashMap.get(targetUserKey));
assertEquals(2, userValue.getX());
assertEquals(3, userValue.getY());
}
}
UserKey에 해당하는 Key 클래스를 MultiKey로 바꾼 것 말고는 실제로 코드를 수정한 것은 아니다. testUnikeyHashMap 메소드의 세 개의 unit test 메소드가 정상적으로 실행될 것이다. 그리고 실제로 x, y 좌표값에 해당하는 value 객체도 얻어진 것을 확인할 수 있다.
댓글 없음:
댓글 쓰기