如何通过Key Predicate获取Map对应值?附Java实际场景
我有一个类型为Map<MidiInstrument, List<JCheckBox>>的instrumentMap,其中MidiInstrument是一个简单的record:
public record MidiInstrument(String name, int firstByte) { }
现在我解析外部文件时只能获取到乐器的名称,需要检查这个Map中是否存在对应名称的乐器,若存在则对其对应的List<JCheckBox>执行操作。我理想中如果有getByPredicate()方法的话,可以这样实现:
List<JCheckBox> checkBoxList = instrumentMap.getByPredicate(instrument -> instrument.name().equals(instrumentName)); if (checkBoxList != null) { // 执行业务逻辑 }
但遗憾的是Map本身没有这类方法,想问有没有类似的方法(包括第三方库)可以实现这个功能?
更新:我自己找到了一个解决方案,但实现起来比较繁琐:
private class LoadButtonListener implements ActionListener { @Override public void actionPerformed(ActionEvent event) { JFileChooser fileChooser = new JFileChooser(); fileChooser.showSaveDialog(mainPanel); try (BufferedReader reader = new BufferedReader(new FileReader(fileChooser.getSelectedFile()))) { String line; while ((line = reader.readLine()) != null) { String[] keyValuePair = line.split(KEY_VALUE_SEPARATOR); Optional<Map.Entry<MidiInstrument, List<JCheckBox>>> optionalEntry; if ((optionalEntry = instrumentMap.entrySet().stream() .filter(entry -> entry.getKey().name().equals(keyValuePair[0])) .findFirst()) // 要是有getByPredicate()就不用这么麻烦的stream了... .isPresent()) { String[] marks = keyValuePair[1].split(""); List<JCheckBox> checkBoxList = optionalEntry.get().getValue(); for (int i = 0; i < checkBoxList.size(); i++) { var currentCheckBox = checkBoxList.get(i); if (marks[i].equals(CHECK_MARK)) { currentCheckBox.setSelected(true); } else if (marks[i].equals(BLANK_MARK)) { currentCheckBox.setSelected(false); } } } } } catch (IOException e) { throw new RuntimeException(e); } } }
一、优化标准Java Stream实现
你当前用Stream的方式是可行的,但可以简化写法,避免临时变量和冗长的判断:
// 针对单个instrumentName的查找简化版 instrumentMap.entrySet().stream() .filter(entry -> entry.getKey().name().equals(instrumentName)) .findFirst() .ifPresent(entry -> { List<JCheckBox> checkBoxList = entry.getValue(); // 在这里直接执行业务逻辑,比如你的勾选操作 });
在你的文件读取代码里,可以把逻辑进一步简化:
private class LoadButtonListener implements ActionListener { @Override public void actionPerformed(ActionEvent event) { JFileChooser fileChooser = new JFileChooser(); fileChooser.showSaveDialog(mainPanel); try (BufferedReader reader = new BufferedReader(new FileReader(fileChooser.getSelectedFile()))) { String line; while ((line = reader.readLine()) != null) { String[] keyValuePair = line.split(KEY_VALUE_SEPARATOR); String targetName = keyValuePair[0]; // 用stream+ifPresent简化判断逻辑 instrumentMap.entrySet().stream() .filter(entry -> entry.getKey().name().equals(targetName)) .findFirst() .ifPresent(entry -> { String[] marks = keyValuePair[1].split(""); List<JCheckBox> checkBoxList = entry.getValue(); for (int i = 0; i < checkBoxList.size(); i++) { JCheckBox currentCheckBox = checkBoxList.get(i); currentCheckBox.setSelected(marks[i].equals(CHECK_MARK)); } }); } } catch (IOException e) { throw new RuntimeException(e); } } }
这样就省去了Optional临时变量和isPresent()判断,代码更简洁紧凑。
二、预建反向索引(推荐)
如果需要频繁根据名称查找,最好预先建立一个Map<String, List<JCheckBox>>的反向映射,这样每次查找都是O(1)的时间复杂度,比每次遍历Map高效得多:
// 初始化时构建反向映射(只需要执行一次) Map<String, List<JCheckBox>> nameToCheckBoxMap = instrumentMap.entrySet().stream() .collect(Collectors.toMap( entry -> entry.getKey().name(), Map.Entry::getValue )); // 使用时直接通过名称获取 List<JCheckBox> checkBoxList = nameToCheckBoxMap.get(instrumentName); if (checkBoxList != null) { // 执行业务逻辑 }
在你的文件读取场景中,这种方式会大幅提升性能,尤其是当instrumentMap元素较多时。
三、第三方库实现
如果一定要用类似getByPredicate的方法,可以使用以下第三方库:
1. Apache Commons Collections
使用MapUtils类的getEntry方法,结合Predicate实现匹配查找:
import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Predicate; // 定义匹配名称的Predicate Predicate<Map.Entry<MidiInstrument, List<JCheckBox>>> nameMatch = entry -> entry.getKey().name().equals(instrumentName); // 获取匹配的条目 Map.Entry<MidiInstrument, List<JCheckBox>> entry = MapUtils.getEntry(instrumentMap, nameMatch); if (entry != null) { List<JCheckBox> checkBoxList = entry.getValue(); // 执行业务逻辑 }
2. Guava(Google Core Libraries)
Guava的Maps类可以通过filterKeys方法过滤出匹配的键,再获取对应值:
import com.google.common.collect.Maps; // 过滤出名称匹配的条目 Map<MidiInstrument, List<JCheckBox>> filteredMap = Maps.filterKeys(instrumentMap, instrument -> instrument.name().equals(instrumentName)); if (!filteredMap.isEmpty()) { List<JCheckBox> checkBoxList = filteredMap.values().iterator().next(); // 执行业务逻辑 }
不过这种方式会创建一个过滤后的视图,性能不如直接用Stream或反向索引。
内容的提问来源于stack exchange,提问作者Sergey Zolotarev

