Skip to content
Advertisement

Why do assertions in Java act as a “missing return statement” whereas throw statements do not?

I have a class with the following field and method:

private final Map<Character, String> charMap = new LinkedHashMap<>();

public Character charOf(String s) {
    assert this.charMap.containsValue(s);
    for (Character c : this.charMap.keySet()) {
        if (this.charMap.get(c).equals(s)) return c;
    }
}

The compiler doesn’t like this, giving me a “missing return statement” error, whereas this compiles fine:

private final Map<Character, String> charMap = new LinkedHashMap<>();

public Character charOf(String s) {
    for (Character c : this.charMap.keySet()) {
        if (this.charMap.get(c).equals(s)) return c;
    }
    throw new IllegalArgumentException("There is no mapping for "" + s + """);
}

As far as I can tell, these two methods should function exactly the same and do the exact same thing with the former being slighly more readable (at the expense of a less detailed error message). It will always either return a value or throw an exception. Why does the compiler not realize this?

Answer

There are two reasons:

  1. The complier is not ‘smart’ enough to figure out that this.charMap.containsValue(s) being true means this.charMap.get(c).equals(s) must be true for some c. It only does simpler analysis, like checking if both branches of an if-statement have a return in them.
  2. Even if it were smart enough, Java is a language with mutable objects and threads – even if the map contains the key at the time of the assert, it may be removed from the map by another thread before the loop starts.

If you want a language with a sufficiently ‘smart’ compiler, you might want to look at dependently-typed languages like Idris.

Advertisement