Используйте эту библиотеку инструментов Java с пользой, и объем кода сразу сократится на 50 %!
Guava — это набор инструментов расширения библиотеки классов Java, разработанный Google, который содержит множество API-интерфейсов, охватывающих коллекции, кэширование, параллелизм, ввод-вывод и многие другие аспекты. С одной стороны, использование этих API может упростить наш код и сделать его более элегантным, с другой — дополнить многие функции, которых нет в jdk, что может сделать нашу разработку более эффективной.
Сегодня я поделюсь с вами некоторыми Map
кокетливыми операциями, инкапсулированными в Guava, После использования этих функций я должен сказать что-то действительно приятное. Сначала введите зависимые координаты, а затем начните наш формальный опыт~
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
Таблица — Двойной ключ Карта
Только один и один Map
разрешены в java , но один может существовать в guava . Две in называются и , то есть строка и столбец соответственно. (Но я лично считаю, что не очень точно понимать их как строки и столбцы. Возможно, более уместно рассматривать их как два столбца)key
value
Table
value
key
Table
key
rowKey
columnKey
Чтобы привести простой пример, если вы хотите записать количество дней, которые сотрудник работает каждый месяц. Если вы используете обычную Map
реализацию в java, вам нужны два уровня вложенности:
Map<String,Map<String,Integer>> map=new HashMap<>();
//存放元素
Map<String,Integer> workMap=new HashMap<>();
workMap.put("Jan",20);
workMap.put("Feb",28);
map.put("Hydra",workMap);
//Получение элементов
Integer dayCount = map.get("Hydra").get("Jan");
Это очень просто , если вы его используете Table
, взгляните на упрощенный код:
Table<String,String,Integer> table= HashBasedTable.create();
//存放元素
table.put("Hydra", "Jan", 20);
table.put("Hydra", "Feb", 28);
table.put("Стволы", "Ян", 28);
table.put("Стволы", "Фев", 16);
//Получить элементы
Integer dayCount = table.get("Hydra", "Feb");
Нам больше не нужно строить сложный двухслойный слой Map
, мы можем сделать это прямо в одном слое. Помимо доступа к элементам, рассмотрим другие практические операции.
1. Получите набор ключа или значения
//rowKey或columnKey的集合
Set<String> rowKeys = table.rowKeySet();
Set<String> columnKeys = table.columnKeySet();
//valueCollection
<Integer> values = table.values();
Выведите их результаты отдельно, key
коллекция не содержит повторяющихся элементов и value
коллекция содержит все элементы без дедупликации:
[Hydra, Trunks]
[Jan, Feb]
[20, 28, 28, 16]
2. Подсчитать сумму всех значений, соответствующих ключу
В качестве примера возьмем сумму всех rowKey
соответствующих статистик:value
for (String key : table.rowKeySet()) {
Set<Map.Entry<String, Integer>> rows = table.row(key).entrySet();
int total = 0;
for (Map.Entry<String, Integer> row : rows) {
total += row.getValue();
}
System.out.println(key + ": " + total);
}
Распечатать результат:
Hydra: 48
Trunks: 44
3. Преобразование rowKey и columnKey
Эту операцию также можно понимать как перестановку строк и столбцов, Tables
статический метод , вызываемый напрямую transpose
:
Table<String, String, Integer> table2 = Tables.transpose(table);
Set<Table.Cell<String, String, Integer>> cells = table2.cellSet();
cells.forEach(cell->
System.out.println(cell.getRowKey()+","+cell.getColumnKey()+":"+cell.getValue())
);
Используйте cellSet
метод, чтобы получить все строки данных, распечатать результаты, и вы увидите, row
что column
обмен произошел:
Jan,Hydra:20
Feb,Hydra:28
Jan,Trunks:28
Feb,Trunks:16
4. Преобразование во вложенную карту
Вы все еще помните формат, в котором мы Table
храним данные перед использованием? Если вы хотите восстановить данные во вложенной Map
форме, вы можете использовать для Table
этого метод or rowMap
:columnMap
Map<String, Map<String, Integer>> rowMap = table.rowMap();
Map<String, Map<String, Integer>> columnMap = table.columnMap();
Просмотрите содержимое в преобразованном формате Map
, которое суммируется по строке и столбцу:
{Hydra={Jan=20, Feb=28}, Trunks={Jan=28, Feb=16}}
{Jan={Hydra=20, Trunks=28}, Feb={Hydra=28, Trunks=16}}
BiMap — двунаправленная карта
В общем Map
, если вы хотите найти value
соответствующий объект key
, нет простого пути.Используете ли вы for
цикл или итератор, вам нужно пройти весь объект Map
. Например, в цикле keySet
:
public List<String> findKey(Map<String, String> map, String val){
List<String> keys=new ArrayList<>();
for (String key : map.keySet()) {
if (map.get(key).equals(val))
keys.add(key);
}
return keys;
}
Однако Guava BiMap
предоставляет структуру данных, связанную key
с value
двумя направлениями.Давайте рассмотрим простой пример:
HashBiMap<String, String> biMap = HashBiMap.create();
biMap.put("Hydra","Programmer");
biMap.put("Tony","IronMan");
biMap.put("Thanos","Titan");
//使用key获取value
System.out.println(biMap.get("Tony"));
BiMap<String, String> inverse = biMap.inverse();
// 使用value获取key
System.out.println(inverse.get("Titan"));
Результаты:
IronMan
Thanos
Выглядит очень полезно, не так ли? Тем не менее, есть еще несколько подводных камней, которых следует избегать при использовании, которые будут разобраны ниже.
1. Влияние операции после обращения
Выше мы использовали inverse
метод для реверсирования исходного BiMap
сопоставления ключ-значение, но этот реверсированный объект BiMap
не является новым объектом, он реализует ассоциацию представлений, поэтому BiMap
все операции, выполняемые с реверсивным, будут действовать на исходный BiMap
вышестоящий.
HashBiMap<String, String> biMap = HashBiMap.create();
biMap.put("Hydra","Programmer");
biMap.put("Tony","IronMan");
biMap.put("Thanos","Titan");
BiMap<String, String> inverse = biMap.inverse();
inverse.put("Железный Человек","Старк");
System.out.println(biMap);
BiMap
После изменения содержимого в инвертированном BiMap
снова посмотрите на исходное содержимое:
{Hydra=Programmer, Thanos=Titan, Stark=IronMan}
IronMan
Видно, что ключ, соответствующий исходному значению Tony
, хотя и не подвергался непосредственному изменению, но теперь ключ изменился Stark
.
2. Значение не может повторяться
BiMap
Лежащее в основе наследование Map
, мы знаем, что дублирование не разрешено Map
в , а двусторонняя нейтрализация может считаться находящейся в эквивалентном положении, поэтому на этом основании добавляются ограничения, а дублирование не допускается. Взгляните на код ниже:key
BiMap
key
value
value
HashBiMap<String, String> biMap = HashBiMap.create();
biMap.put("Tony","IronMan");
biMap.put("Stark","IronMan");
Таким образом, код не может завершиться нормально, и IllegalArgumentException
будет выброшено исключение:
Если вы действительно хотите сопоставить новый key
с существующим value
, вы также можете использовать forcePut
метод для принудительной замены исходного key
:
HashBiMap<String, String> biMap = HashBiMap.create();
biMap.put("Tony","IronMan");
biMap.forcePut("Stark","IronMan");
Распечатайте замененное BiMap
:
{Stark=IronMan}
Кстати, поскольку BiMap
он value
не допускает повторения, его values
метод возвращает не повторение Set
, а обычный Collection
:
Set<String> values = biMap.values();
Multimap — многозначная карта
Java Map
поддерживает отношение ключ-значение один к одному. Если вы хотите сопоставить ключ с несколькими значениями, вы можете установить содержимое значения только как коллекцию. Простая реализация выглядит следующим образом:
Map<String, List<Integer>> map=new HashMap<>();
List<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
map.put("day",list);
Guava Multimap
предоставляет форму сопоставления ключа с несколькими значениями. Ему не нужно определять сложные внутренние коллекции. Вы можете использовать его как обычный. Map
Определите и поместите данные следующим образом:
Multimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.put("day",1);
multimap.put("day",2);
multimap.put("day",8);
multimap.put("month",3);
Распечатайте Multimap
содержимое этого, и вы сможете интуитивно увидеть, что каждое key
соответствующее является коллекцией:
{month=[3], day=[1, 2, 8]}
1. Получите набор значений
В приведенной выше операции созданный обычный метод Multimap
вернет get(key)
коллекцию Collection
типа:
Collection<Integer> day = multimap.get("day");
Если он указан как ArrayListMultimap
тип во время создания, get
метод вернет List
:
ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
List<Integer> day = multimap.get("day");
Точно так же можно создавать и HashMultimap
т.п. TreeMultimap
типы Multimap
.
Multimap
Метод get
вернет не null
-коллекцию, но содержимое этой коллекции может быть пустым, взгляните на следующий пример:
List<Integer> day = multimap.get("day");
List<Integer> year = multimap.get("year");
System.out.println(day);
System.out.println(year);
Распечатать результат:
[1, 2, 8]
[]
2. Сбор после операции получить
Подобно BiMap
использованию , get
коллекция, возвращаемая методом, не является независимым объектом. Ее можно понимать как ассоциацию представления коллекции. Операция над этой новой коллекцией по-прежнему будет воздействовать на исходную . Взгляните на Multimap
следующее пример:
ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.put("day",1);
multimap.put("day",2);
multimap.put("day",8);
multimap.put("month",3);
Список<Целое> день = multimap.get("день");
Список<Целое> месяц = multimap.get("месяц");
day.remove(0);//Этот 0 является индексом
month.add(12);
System.out.println(multimap);
Проверьте измененные результаты:
{month=[3, 12], day=[2, 8]}
3. Преобразовать в карту
С помощью asMap
метода его можно Multimap
преобразовать в Map<K,Collection>
форму, и это Map
тоже можно рассматривать как связанное представление, и Map
операция над этим будет действовать на исходное Multimap
.
Map<String, Collection<Integer>> map = multimap.asMap();
for (String key : map.keySet()) {
System.out.println(key+" : "+map.get(key));
}
map.get("day").add(20);
System.out.println(multimap);
Результаты:
month : [3]
day : [1, 2, 8]
{month=[3], day=[1, 2, 8, 20]}
4. Проблемы с количеством
Multimap
Число in также несколько сбивает с толку при использовании, сначала взгляните на следующий пример:
System.out.println(multimap.size());
System.out.println(multimap.entries().size());
for (Map.Entry<String, Integer> entry : multimap.entries()) {
System.out.println(entry.getKey()+","+entry.getValue());
}
Распечатать результат:
4
4
month,3
day,1
day,2
day,8
Это связано с тем, что size()
метод возвращает все сопоставления key
к одному value
, поэтому результат равен 4, entries()
а метод тот же, возвращая набор пар ключ-значение key
с одним . value
Но он keySet
хранит другое key
число, например, результат, напечатанный следующей строкой кода, будет 2.
System.out.println(multimap.keySet().size());
Затем посмотрите на преобразование его в Map
, сумма изменится:
Set<Map.Entry<String, Collection<Integer>>> entries = multimap.asMap().entrySet();
System.out.println(entries.size());
Результат выполнения кода равен 2, потому что он получил отношение отображения key
к Collection
.
RangeMap - Карта диапазона
Давайте сначала рассмотрим пример, предположим, что мы хотим классифицировать результаты тестов на основе результатов, тогда в коде будет что-то уродливое if-else
:
public static String getRank(int score){
if (0<=score && score<60)
return "fail";
else if (60<=score && score<=90)
return "satisfactory";
else if (90<score && score<=100)
return "excellent";
return null;
}
Описание в гуаве RangeMap
описывает отношение отображения интервала к определенному значению, что позволяет нам писать код более элегантным способом. Давайте RangeMap
изменим приведенный выше код и протестируем его:
RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closedOpen(0,60),"fail");
rangeMap.put(Range.closed(60,90),"satisfactory");
rangeMap.put(Range.openClosed(90,100),"excellent");
System.out.println(rangeMap.get(59));
System.out.println(rangeMap.get(60));
System.out.println(rangeMap.get(90));
System.out.println(rangeMap.get(91));
В приведенном выше коде [0,60)
закрытые слева и открытые справа интервалы, [60,90]
закрытые интервалы, а также (90,100]
открытые слева и закрытые справа интервалы создаются последовательно и сопоставляются с определенными значениями соответственно. Текущий результат печатает:
fail
satisfactory
satisfactory
excellent
[70,80]
Конечно, мы также можем удалить раздел пробела.После того, как следующий код удалит этот замкнутый интервал, get
при повторном выполнении будет возвращен результат null
:
rangeMap.remove(Range.closed(70,80));
System.out.println(rangeMap.get(75));
ClassToInstanceMap — карта экземпляра
ClassToInstanceMap
является специальным Map
, его ключом Class
и значением является Class
соответствующий объект экземпляра. Давайте сначала рассмотрим простой пример использования putInstance
методов для хранения объектов:
ClassToInstanceMap<Object> instanceMap = MutableClassToInstanceMap.create();
User user=new User("Hydra",18);
Dept dept=new Dept("develop",200);
instanceMap.putInstance(Пользователь.класс,пользователь);
instanceMap.putInstance(Отдел.класс,отдел);
Используйте getInstance
метод для получения объекта:
User user1 = instanceMap.getInstance(User.class);
System.out.println(user==user1);
Результат операции печатается true
, указывая, что объект, который был извлечен, действительно является объектом, который мы создали и вставили ранее.
Вы можете задаться вопросом, если вы просто сохраняете объекты, Map<Class,Object>
вы также можете использовать обычные объекты, например:
Map<Class,Object> map=new HashMap<>();
User user=new User("Hydra",18);
Dept dept=new Dept("develop",200);
map.put(User.class,user);
map.put(Dept.class,dept);
Итак, ClassToInstanceMap
каковы преимущества использования этого подхода?
Во-первых, самое очевидное здесь то, что сложное обязательное приведение типов при извлечении объекта опускается, а также избегается ошибка ручного приведения типов. Во-вторых, мы можем взглянуть на ClassToInstanceMap
определение интерфейса, которое является общим:
public interface ClassToInstanceMap<B> extends Map<Class<? extends B>, B>{...}
Этот универсальный тип также может играть роль в ограничении типа. value
Чтобы соответствовать key
соответствующему типу, взгляните на следующий пример:
ClassToInstanceMap<Map> instanceMap = MutableClassToInstanceMap.create();
HashMap<String, Object> hashMap = new HashMap<>();
TreeMap<String, Object> treeMap = new TreeMap<>();
ArrayList<Object> list = new ArrayList<>();
instanceMap.putInstance(HashMap.class,hashMap);
instanceMap.putInstance(TreeMap.class,treeMap);
Это может быть выполнено обычным образом, потому что HashMap
и TreeMap
оба интегрируют Map
родительский класс, но если вы хотите поместить другие типы, вы скомпилируете и сообщите об ошибке.
Итак, если вы хотите кэшировать объекты, но не хотите делать сложную проверку типов, то можете использовать что угодно ClassToInstanceMap
.
Подведем итог
В этой статье представлены 5 видов Map
расширенных структур данных в гуаве, которые предоставляют очень практичные функции и могут значительно упростить наш код. Но в то же время есть много ловушек, которых нужно избегать, например, изменение связанного представления повлияет на исходные данные и т. д. Вы должны быть более осторожными в конкретном использовании.