Collections.unmodifiableMap 메소드를 이용한 read-only 객체 만들기

웹 애플리케이션 기동 시 properties에 있는 설정 정보들을 컬렉션 객체에 저장하고 이를 가져다가 쓰는 경우가 많은데 이 객체에 변경을 막는 제약 조건을 걸고 싶을 때가 있다.

즉, read-only 한 객체를 만들고 싶은 경우인데 이럴 때 다음의 메소드를 사용하면 좋을 것 같다.

Collections.unmodifiableMap

Collections.unmodifiableList

해당 메소드는 파라미터로 전달 받은 컬렉션 객체에 어떠한 변경이라도 발생하면 예외를 return한다.

테스트 코드는 다음과 같다.

package collection;

 

import static org.hamcrest.CoreMatchers.*;

import static org.junit.Assert.*;

 

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;

 

import org.junit.Test;

 

public class CollectionsTest {

 

        @Test

        public void unmodifyMapTest() {

               Map<Integer,
String> testMap =
new HashMap<Integer, String>();

               testMap.put(1,
“test1”);

               testMap.put(2,
“test2”);

 

               Map<Integer,
String> unmodifyTestMap = Collections.unmodifiableMap(testMap);

               assertThat(“test1”, is(unmodifyTestMap.get(1)));

               assertThat(“test2”, is(unmodifyTestMap.get(2)));

 

               boolean exceptionThrown = false;

               try {

                       unmodifyTestMap.put(3,
“test3”); //
unmodifyTestMap
객체 변경 시 예외

               }
catch (UnsupportedOperationException ex) {

                       ex.printStackTrace();

                       exceptionThrown
=
true;

               }

 

               assertTrue(exceptionThrown);

        }

}

문득 어떻게 해서 객체의 변경을 감지한 후 예외를 던지는지 궁금해 졌다.

unmodifiableMap 메소드는 다음과 같은 구조를 가지고 있고, UnmodifiableMap 클래스의 타입을 return한다.

public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {

        return new
UnmodifiableMap<K,V>(m);

    }

자~ 그럼 UnmodifiableMap 클래스를 들여다 보자.

Map 인터페이스를 구현한 UnmodifiableMap 클래스에서 put, remove, putAll 호출 시 UnsupportedOperationException 예외를 return하도록 설계되어 있다. (생각보다 간단하게 구현되어 있음. 역시 Java API)

private static class UnmodifiableMap<K, V> implements Map<K, V>, Serializable {

        // use serialVersionUID from JDK 1.2.2 for
interoperability

        private static final long serialVersionUID = -1034234728574286014L;

 

        private final Map<? extends K, ?
extends V> m;

 

        UnmodifiableMap(Map<?
extends K, ? extends V> m) {

               if (m == null)

                       throw new NullPointerException();

               this.m = m;

        }

 

        public int size() {

               return m.size();

        }

 

        public boolean isEmpty() {

               return m.isEmpty();

        }

 

        public boolean containsKey(Object key) {

               return m.containsKey(key);

        }

 

        public boolean containsValue(Object val) {

               return m.containsValue(val);

        }

 

        public V get(Object key) {

               return m.get(key);

        }

 

        public V put(K key, V value) {

               throw new UnsupportedOperationException();

        }

 

        public V remove(Object key) {

               throw new UnsupportedOperationException();

        }

 

        public void putAll(Map<? extends K, ?
extends V> m) {

               throw new UnsupportedOperationException();

        }

 

        public void clear() {

               throw new UnsupportedOperationException();

        }

}

인터페이스를 이용한 느슨한 설계가 어떤 장점을 주는지 확인할 수 있는 좋은 예인 것 같다.