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 HashMaptypeCastConvert(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); HashMaphMap = 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 HashMapguavaConvert(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 HashMaploopConvert(Properties prop) { HashMap retMap = new HashMap<>(); for (Map.Entry
В этом методе мы перебираем Свойства так же, как и для типичной карты . В результате мы имеем индивидуальный доступ к каждому значению пары ключей, представленному классом Map.Entry .
Прежде чем поместить значения в возвращаемую HashMap , мы можем выполнить дополнительные проверки, поэтому мы решаем использовать метод String.valueOf () .
4.2. API потоков и коллекторов
Мы даже можем рефакторировать наш метод, используя современный способ Java 8:
public HashMapstreamConvert(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); HashMaphMap1 = 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 .