1. 소개

많은 개발자가 소스 코드 외부에 애플리케이션 매개변수를 저장하기로 결정합니다. Java에서 그렇게 하는 방법 중 하나는 외부 구성 파일을 사용하고 java.util.Properties 클래스를 통해 읽는 것입니다.

이 사용방법(예제)에서는 java.util.PropertiesHashMap<String, String> 으로 변환하는 다양한 접근 방식에 중점을 둘 것 입니다. 일반 Java, 람다 또는 외부 라이브러리 를 사용하여 목표를 달성하기 위해 다양한 방법을 구현할 것 입니다. 예제를 통해 각 솔루션의 장단점에 대해 논의합니다.

2. HashMap 생성자

첫 번째 코드를 구현하기 전에 java.util.Properties 에 대한 Javadoc을 확인하십시오 . 보시다시피 이 유틸리티 클래스는 Map 인터페이스 도 구현하는 Hashtable<Object, Object> 에서 상속합니다. 또한 Java는 ReaderWriter 클래스를 래핑하여 문자열에 대해 직접 작업 합니다.

해당 정보에 따라 속성유형 변환 및 생성자 호출을 사용하여 HashMap<String, String> 으로 변환할 수 있습니다.

속성 을 올바르게 로드했다고 가정하면 다음 을 구현할 수 있습니다.

public static HashMap<String, String> typeCastConvert(Properties prop) {
    Map step1 = prop;
    Map<String, String> step2 = (Map<String, String>) step1;
    return new HashMap<>(step2);
}

여기에서는 세 가지 간단한 단계로 변환을 구현합니다.

먼저 상속 그래프에 따라 속성 을 원시 Map 으로 캐스팅해야 합니다 . 이 조치는 @SuppressWarnings("rawtypes") 어노테이션 을 사용하여 비활성화할 수 있는 첫 번째 컴파일러 경고를 강제합니다 .

그런 다음 원시 MapMap<String, String> 으로 캐스팅하여 @SupressWarnings("unchecked") 를 사용하여 생략할 수 있는 또 다른 컴파일러 경고를 발생시킵니다 .

마지막으로 복사 생성자 를 사용하여 HashMap 을 빌드합니다 . 이것은 Properties 를 변환하는 가장 빠른 방법 이지만 이 솔루션에는 유형 안전성 과 관련된 큰 단점도 있습니다 . 변환 전에 속성 이 손상되고 수정될 수 있습니다.

설명서에 따르면 Properties 클래스에는 문자열을 강제로 사용하는 setProperty()getProperty() 메서드가 있습니다. 그러나 또한 Hashtable 에서 상속된 put( )putAll() 메서드가 있어 속성 에서 키 또는 값으로 모든 유형을 사용할 수 있습니다 .

properties.put("property4", 456);
properties.put(5, 10.11);

HashMap<String, String> hMap = typeCastConvert(properties);
assertThrows(ClassCastException.class, () -> {
    String s = hMap.get("property4");
});
assertEquals(Integer.class, ((Object) hMap.get("property4")).getClass());

assertThrows(ClassCastException.class, () -> {
    String s = hMap.get(5);
});
assertEquals(Double.class, ((Object) hMap.get(5)).getClass());

보시다시피 변환은 오류 없이 실행되지만 HashMap 의 모든 요소 가 strings는 아닙니다 . 그래서 이 방법이 가장 쉬워 보여도 앞으로 몇 가지 안전 관련 점검 사항을 염두에 두어야 합니다 .

3. 구아바 API

타사 라이브러리를 사용할 수 있다면 Google Guava API 가 유용합니다. 이 라이브러리 는 거의 모든 작업을 수행 하는 정적 Maps.fromProperties() 메서드를 제공합니다. 문서에 따르면 이 호출은 ImmutableMap 을 반환하므로 HashMap 을 갖고 싶다면 다음을 사용할 수 있습니다.

public HashMap<String, String> guavaConvert(Properties prop) {
    return Maps.newHashMap(Maps.fromProperties(prop));
}

이전과 마찬가지로 이 방법 은 속성 에 문자열 값만 포함되어 있다고 완전히 확신할 때 제대로 작동 합니다. 일부 부적합한 값이 있으면 예기치 않은 동작이 발생합니다.

properties.put("property4", 456);
assertThrows(NullPointerException.class, 
    () -> PropertiesToHashMapConverter.guavaConvert(properties));

properties.put(5, 10.11);
assertThrows(ClassCastException.class, 
    () -> PropertiesToHashMapConverter.guavaConvert(properties));

Guava API는 추가 매핑을 수행하지 않습니다. 결과적으로 이러한 Properties 를 변환할 수 없으며 예외가 발생합니다.

첫 번째 경우 속성 에서 검색할 수 없는  Integer 값 으로 인해 NullPointerException 이 throw됩니다. getProperty() 메서드를 사용하고 결과적으로 null 로 해석됩니다 . 두 번째 예는 입력 속성 맵에서 발생하는 비문자열 키  와 관련된 ClassCastException 을 발생시킵니다.

이 솔루션은 더 나은 유형 제어를 제공 하고 변환 프로세스 중에 발생하는 위반을 알려줍니다.

4. Custom형 유형 안전 구현

이제 이전 예제의 안전 문제 유형을 해결할 때입니다. 아시다시피 Properties 클래스는 Map 인터페이스 에서 상속된 메서드를 구현합니다 . Map 을 반복 하는 가능한 방법 중 하나를 사용 하여 적절한 솔루션을 구현하고 유형 검사를 통해 풍부하게 합니다.

4.1. for 루프 를 사용한 반복

간단한 for 루프를 구현해 보겠습니다.

public HashMap<String, String> loopConvert(Properties prop) {
    HashMap<String, String> retMap = new HashMap<>();
    for (Map.Entry<Object, Object> entry : prop.entrySet()) {
        retMap.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
    }
    return retMap;
}

이 방법에서는 일반적인 Map 에 대해 수행하는 것과 동일한 방식으로 Properties 를 반복합니다 . 결과적으로 Map.Entry 클래스 가 나타내는 모든 단일 키 쌍 값에 하나씩 액세스할 수 있습니다 .

반환된 HashMap 에 값을 넣기 전에 추가 검사를 수행할 수 있으므로 String.valueOf() 메서드를 사용하기로 결정합니다.

4.2. 스트림수집기 API

최신 Java 8 방식을 사용하여 방법을 리팩토링할 수도 있습니다.

public HashMap<String, String> streamConvert(Properties prop) {
    return prop.entrySet().stream().collect(
      Collectors.toMap(
        e -> String.valueOf(e.getKey()),
        e -> String.valueOf(e.getValue()),
        (prev, next) -> next, HashMap::new
    ));
}

이 경우  명시적인 HashMap 구성 없이 Java 8 스트림 수집기 를 사용하고 있습니다. 이 메서드는 이전 예제에서 소개한 것과 정확히 동일한 논리를 구현합니다.

두 솔루션 모두 유형 변환 및 구아바 예제가 필요로 하지 않는 일부 사용자 지정 구현이 필요하기 때문에 약간 더 복잡합니다 .

그러나 결과 HashMap 에 값 을 넣기 전에 값에 액세스 할 수 있으므로 추가 검사 또는 매핑을 구현할 수 있습니다 .

properties.put("property4", 456);
properties.put(5, 10.11);

HashMap<String, String> hMap1 = loopConvert(properties);
HashMap<String, String> hMap2 = streamConvert(properties);

assertDoesNotThrow(() -> {
    String s1 = hMap1.get("property4");
    String s2 = hMap2.get("property4");
});
assertEquals("456", hMap1.get("property4"));
assertEquals("456", hMap2.get("property4"));

assertDoesNotThrow(() -> {
    String s1 = hMap1.get("property4");
    String s2 = hMap2.get("property4");
});
assertEquals("10.11", hMap1.get("5"));
assertEquals("10.11", hMap2.get("5"));

assertEquals(hMap2, hMap1);

보시다시피 문자열이 아닌 값과 관련된 문제를 해결했습니다. 이 접근 방식을 사용하면 매핑 논리를 수동으로 조정하여 적절한 구현을 달성할 수 있습니다.

5. 결론

이 사용방법(예제)에서는 java.util.PropertiesHashMap<String, String> 으로 변환하는 다양한 접근 방식을 확인 했습니다.

우리는 아마도 가장 빠른 변환이지만 컴파일러 경고 및 잠재적인 유형 안전 오류 를 가져오는 유형 캐스팅 솔루션으로 시작했습니다 .

그런 다음 컴파일러 경고를 해결하고 오류 처리를 위한 몇 가지 개선 사항을 제공하는 Guava API를 사용하는 솔루션을 살펴보았습니다.

마지막으로 유형 안전 오류를 처리하고 대부분의 제어 기능을 제공하는 사용자 지정 메서드를 구현했습니다.

이 사용방법(예제)의 모든 코드 스니펫은 GitHub 에서 사용할 수 있습니다 .

Generic footer banner