1

On an app I have buses, each bus is associated with several stations and also has several arrival times to each station during a day long time.

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

The first for loop was intended to add several stations (each station with several times) to a bus. the inner for loop, however, is used because I have not known any other way to get the key of busTimesForSingleStation. (even though each time there's only one single entry in the busTimesForSingleStation map.)

My specific problem is that the key variable which is used on the penultimate line apparently doesn't work and I get this error from IDE:

Variable 'key' might not have been initialized

DummyBeginner
  • 411
  • 10
  • 34
  • @MohamedBathaoui Thanks. But even after assigning an initial value, I still had the issue: https://stackoverflow.com/questions/35688822/extracted-map-key-to-string-could-not-be-used/35689001#comment59055647_35688883 – DummyBeginner Feb 28 '16 at 22:55

2 Answers2

2

I agree with SimMac's description of the problem, but disagree with his suggested fix.

The problem with saying simply String key = ""; is that you then use this value. Maybe this is OK - that stationMap.get(key) returns a safe "not found" value - but much more likely it doesn't. Say it returns null: now you're adding null to stationsOfBus, which may fail at runtime (best case - but it may fail a long way from this code), or silently continue to propagate nonsense through your program (worst case).

It is much better to fix the actual problem that was caught by the compiler (that the map might be empty) than to hack a fix which just hides that problem.

If it is the case that busTimesForSingleStation can't be empty, then assert that fact:

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!");
}

Or, if it could be empty, handle that explicitly:

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.
}

The deeper cause of this problem is that your choice of return type of generateTimesForStation does not accurately encode your intent:

  • your question says that you expect the returned map to contain one key/value pair
  • your return type says that callers of the method should expect to be returned zero, one or more key/value pairs

Obviously, "one k/v pair" can be represented in a data structure containing "zero or more k/v pairs" - but choosing to so represent it means that calling code must be able to handle the "zero" and "or more" cases too.

For instance, if the returned data structure did contain 2 or more k/v pairs, and you want to pick the "last" key, you'd have to worry about the iteration ordering of those pairs. A requirement to handle such a case is completely removed if the type system can guarantee that it will never occur.

If you want to return exactly one k/v pair, there are two options to highlight:

  • The simplest change from your code is to use Map.Entry<String, String[]> instead of Map<String, String[]>. A Map.Entry is simply one of the elements of the structure you are iterating over in the loop.

    Map.Entry<String, String[]> entry = generateTimesForStation(i);
    String key = entry.getKey();
    // Do whatever with the key.
    
  • Write a custom type to represent the k/v pair, and return and instance of that. One of the disadvantages of Map.Entry (and, by extension, Map) is that there is no semantic meaning attached to "key" and "value". It might be easier to read the code at a glance if you have accessors called getStationName and getTimes (or whatever). A well-chosen class name is also a lot prettier than the generic soup of 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.
    

Whichever of these you choose, you now avoid the uninitialized variable and loop, since there is nothing to loop over any more.

If you want to return zero or one k/v pairs, the two options above are valid, but you should wrap the result in an Optional-like type in order to strongly indicate the possibility that no such k/v pairs exists. You could return null to indicate that, but I would discourage that - see Louis Wasserman's answer on why Optional should be preferred over null (he is talking about the Guava Optional, but the argument for Java 8 Optional is identical).

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.
}

The approach of using a more specific return type may help in your implementation of the generateTimesForStation too: the constraint that one (or maybe zero or one) values must be returned can help you catch control flow paths which would result in return values that don't meet these constraints.

Community
  • 1
  • 1
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • Thanks for the tips. As for picking the last key of `busTimesForSingleStation`, the thing is that each time there is only one entry in the map certainly and the reason I used `for` for the extracting key was that I have not known how to do it in any other way. (Mentioned in the question). Anyway is that `key = maybeKey;` in the loop which makes the logic uses last key only? (I.e saving keys in array would solve the issue if there were more than one entry in the `busTimesForSingleStation` map?) //// Despite applying your suggested changes, I still get error: https://i.imgur.com/4Pye7nb.png – DummyBeginner Feb 28 '16 at 22:50
  • @DummyBeginner well, I didn't read the question carefully enough :) I've posted a detailed explanation of how this whole problem comes about because of your choice of types. – Andy Turner Feb 29 '16 at 08:28
1

The thing is, the compiler doesn't know if the second for loop will be run at least once (because it can't possibly know if busTimesForSingleStation is empty or not).

To fix this, simply change String key; to String key = ""; so the compiler knows, there won't be a null reference, even if busTimesForSingleStation is empty.

SimMac
  • 479
  • 5
  • 16
  • 1
    It is better to handle the empty case explicitly. Chances are, `stationMap.get("")` will return null, so you've simply converted an easy-to-spot compile-time failure into a runtime failure (which may actually manifest far away from this code). – Andy Turner Feb 28 '16 at 22:10
  • Thanks. after changing to `String key = "";` I got the same error as when I had set `String key = null`. The error is `wrong 1st argument type. Found: 'boolean', required: java.util.List` I don't know why compiler evaluates an empty String as a boolean value. [null] – DummyBeginner Feb 28 '16 at 22:12