Рубрики
Без рубрики

Преобразование свойств Java в HashMap

Автор оригинала: baeldung.

1. введение

Многие разработчики решают хранить параметры приложения вне исходного кода. Один из способов сделать это в Java-использовать внешний конфигурационный файл и читать его через java.util.Класс свойств.

В этом уроке мы сосредоточимся на различных подходах к преобразованию java.util.Свойства в HashMap String> . Мы будем реализовывать различные методы для достижения нашей цели, используя простую Java, lambdas или внешние библиотеки. На примерах мы обсудим плюсы и минусы каждого решения. String>

2. Конструктор HashMap

Прежде чем реализовать наш первый код, давайте проверим Javadoc на наличие java.util.Свойства . Как мы видим, этот служебный класс наследуется от Hashtable Object> , который также реализует интерфейс Map . Более того, Java оборачивает свои классы Reader и Writer для работы непосредственно со значениями String . Object>

Согласно этой информации, мы можем преобразовать Properties в HashMap String> с помощью typecasting и вызовов конструктора. String>

Предполагая, что мы загрузили наши Свойства правильно, мы можем реализовать:

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

Здесь мы реализуем наше преобразование в три простых шага.

Во-первых, согласно графу наследования, нам нужно привести наши Свойства в необработанную карту . Это действие вызовет первое предупреждение компилятора, которое можно отключить с помощью аннотации @SuppressWarnings(“rawtypes”) .

После этого мы приводим наш raw Map в Map String> , вызывая еще одно предупреждение компилятора , которое можно опустить с помощью @SuppressWarnings(“unchecked”) . String>

Наконец, мы строим нашу HashMap с помощью конструктора copy . Это самый быстрый способ преобразования наших свойств , но это решение также имеет большой недостаток, связанный с безопасностью типов : Наши Свойства могут быть скомпрометированы и изменены до преобразования.

Согласно документации, класс Properties имеет методы setProperty() и getProperty () , которые принудительно используют значения String . Но также существуют put() и putAll() методы, унаследованные от Hashtable , которые позволяют использовать любой тип в качестве ключей или значений в наших Свойствах :

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

HashMap 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 являются строками . Таким образом, даже если этот метод выглядит самым простым, мы должны иметь в виду некоторые проверки, связанные с безопасностью в будущем.

3. API Гуавы

Если мы можем использовать сторонние библиотеки, то нам пригодится Google Guava API . Эта библиотека предоставляет статический метод Maps.fromProperties () , который делает почти все за нас. Согласно документации, этот вызов возвращает ImmutableMap , поэтому, если мы хотим иметь HashMap, мы можем использовать:

public HashMap 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));

API Guava не выполняет никаких дополнительных сопоставлений. В результате он не позволяет нам преобразовывать эти Свойства , выбрасывая исключения.

В первом случае NullPointerException выбрасывается из-за значения Integer , которое не может быть извлечено с помощью свойств . getProperty() метод и, как результат, интерпретируется как null . Второй пример вызывает исключение ClassCastException , связанное с ключом non-string , встречающимся на карте входных свойств.

Это решение дает нам лучший контроль типов, а также информирует нас о нарушениях , которые происходят в процессе преобразования.

4. Реализация Безопасности Пользовательского Типа

Теперь пришло время решить проблемы безопасности нашего типа из предыдущих примеров. Как мы знаем, класс Properties реализует методы, унаследованные от интерфейса Map . Мы будем использовать один из возможных способов итерации по карте для реализации правильного решения и обогащения его проверками типов.

4.1. Итерация цикла for

Давайте реализуем простой for -цикл:

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

В этом методе мы перебираем Свойства так же, как и для типичной карты . В результате мы имеем индивидуальный доступ к каждому значению пары ключей, представленному классом Map.Entry .

Прежде чем поместить значения в возвращаемую HashMap , мы можем выполнить дополнительные проверки, поэтому мы решаем использовать метод String.valueOf () .

4.2. API потоков и коллекторов

Мы даже можем рефакторировать наш метод, используя современный способ Java 8:

public HashMap 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
    ));
}

В этом случае мы используем Java 8 Stream Collectors без явной HashMap конструкции. Этот метод реализует точно такую же логику, как и в предыдущем примере.

Оба решения немного сложнее, потому что они требуют некоторой пользовательской реализации , чего нет в примерах typecasting и Guava.

Однако у нас есть доступ к значениям перед тем, как поместить их в результирующую HashMap , поэтому мы можем реализовать дополнительные проверки или сопоставления :

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

HashMap hMap1 = loopConvert(properties);
HashMap 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.Свойства в HashMap String> . String>

Мы начали с решения для типизации, которое, возможно, является самым быстрым преобразованием, но также приносит предупреждения компилятора и потенциальные ошибки безопасности типов .

Затем мы взглянули на решение с использованием Guava API, которое устраняет предупреждения компилятора и вносит некоторые улучшения в обработку ошибок.

Наконец, мы внедрили наши собственные методы, которые имеют дело с ошибками безопасности типов и дают нам наибольший контроль.

Все фрагменты кода из этого учебника доступны на GitHub .