无法使用提取到字符串的映射键

Extracted Map key to String could not be used

在一个应用程序上我有公交车,每辆公交车都与几个车站相关联,并且在一天中的每个车站都有几个到达时间。

Map<String, Station> stationMap = new HashMap<>();


Map<String, String[]> busTimesForSingleStation = new HashMap<>(); // key is station's name and value would be an array of times in string.
List<Station> stationsOfBus = new ArrayList<>();
Map<List<Station>, String[]> allArriveTimes = new HashMap<>();

for (int i = 10; i <= 400; i += 10) {
    busTimesForSingleStation = generateTimeForStation(i);
    String key;
    for (Map.Entry<String, String[]> e : busTimesForSingleStation.entrySet()){
        key = e.getKey();
    }
    allArriveTimes.put(stationsOfBus.add(stationMap.get(key)),
                       busTimesForSingleStation.get(Integer.toString(i)));
}

第一个 for 循环旨在将多个站点(每个站点有多个站点)添加到公共汽车。然而,使用内部 for 循环是因为我不知道任何其他方法来获取 busTimesForSingleStation 的密钥。 (尽管每次 busTimesForSingleStation 地图中只有一个条目。)

我的具体问题是倒数第二行使用的 key 变量显然不起作用,我从 IDE:

得到这个错误

Variable 'key' might not have been initialized

问题是,编译器不知道第二个 for 循环是否会 运行 至少一次(因为它不可能知道 busTimesForSingleStation是否为空)。

要解决这个问题,只需将 String key; 更改为 String key = ""; 这样编译器就知道,不会有空引用,即使 busTimesForSingleStation 是空.

我同意 SimMac 对问题的描述,但不同意他建议的修复方法。

简单地说 String key = ""; 的问题是您随后 使用 这个值。也许这没问题 - stationMap.get(key) return 是一个安全的 "not found" 值 - 但更有可能不是。说它 returns null:现在你正在将 null 添加到 stationsOfBus,这可能会在运行时失败(最好的情况 - 但它可能会从这段代码中失败很长一段时间),或者默默地继续通过你的程序传播废话(最坏的情况)。

修复编译器捕获的实际问题(映射可能为空)比破解一个只隐藏该问题的修复要好得多。

如果 busTimesForSingleStation 不能为空,则断言:

if (!busTimesForSingleStation.isEmpty()) {
  String key = "";  // Now you know it will be overwritten.
  for (String maybeKey : busTimesForSingleStation.keySet()) {
    key = maybeKey;
  }
} else {
  throw new AssertionError("Should never happen!");
}

或者,如果它可能为空,则明确处理:

if (!busTimesForSingleStation.isEmpty()) {
  String key = "";  // Now you know it will be overwritten.
  for (String maybeKey : busTimesForSingleStation.keySet()) {
    key = maybeKey;
  }
} else {
  // Some logic to handle it correctly.
}

这个问题的更深层原因是您选择的 return 类型 generateTimesForStation 没有准确编码您的意图:

  • 您的问题是您希望 returned 地图包含一对 key/value
  • 您的 return 类型表示该方法的调用者应该 returned 零,一对或多对 key/value

显然,"one k/v pair" 可以用包含 "zero or more k/v pairs" 的数据结构表示 - 但选择这样表示意味着调用代码必须能够处理 "zero" 和 "or more" 例。

例如,如果 returned 数据结构 did 包含 2 个或更多 k/v 对,并且您想选择 "last" 键,您将不得不担心这些对的迭代顺序。如果类型系统可以保证它永远不会发生,那么处理这种情况的要求就完全取消了。

如果你想return正好一对k/v,有两个选项需要高亮:

  • 代码中最简单的更改是使用 Map.Entry<String, String[]> 而不是 Map<String, String[]>Map.Entry 只是您在循环中迭代的结构的元素之一。

    Map.Entry<String, String[]> entry = generateTimesForStation(i);
    String key = entry.getKey();
    // Do whatever with the key.
    
  • 编写一个自定义类型来表示 k/v 对,以及 return 和它的实例。 Map.Entry(以及 Map)的缺点之一是 "key" 和 "value" 没有附加语义。如果您有名为 getStationNamegetTimes(或其他名称)的访问器,可能更容易一目了然地阅读代码。一个精心挑选的 class 名字也比 Map.Entry<String, String[]>.

    的普通汤漂亮很多
    BusTime entry = generateTimesForStation(i);
    String key = entry.getStationName();
    // Do whatever with the key - or maybe just refer to
    // `entry.getStationName()` directly.
    

无论你选择哪一个,你现在都避免了未初始化的变量和循环,因为没有任何东西可以循环了。

如果你想 return 零对或一对 k/v ,上面的两个选项都是有效的,但你应该将结果包装在类似 Optional 的类型中,以便强烈表明不存在这样的 k/v 对的可能性。你 可以 return null 来表明这一点,但我不鼓励这样做 - 请参阅 Louis Wasserman's answer 为什么 Optionalnull(他说的是 Guava Optional,但 Java 8 Optional 的论点是相同的)。

Optional<Map.Entry<String, String[]>> entry = generateTimesForStation(i);
if (entry.isPresent()) {
  String key = entry.get().getKey();
  // Do whatever with the key.
} else {
  // Handle the "not found" case.
}

使用更具体的 return 类型的方法也可能有助于您实现 generateTimesForStation:一个(或者可能是零个或一个)值必须是 returned 可以帮助您捕获导致 return 值不符合这些约束的控制流路径。