Nick
Nick

Reputation: 158

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?

Upvotes: 0

Views: 500

Answers (3)

dan1st
dan1st

Reputation: 16348

Because of two things:

Firstly, assertions only throw exceptions if they are enabled at run-time using the jvm argument -ea. This means there is a possibility that is skipped.

Secondly, you throw the exception at the end while you run the assertion before the code. Theoretically, the assertion could be true without the loop returning as the Map content could theoretically change between the assertion and the loop or containsValue could do something the compiler doesn't expect. The compiler just checks for sytax, not for logical impossibilities.

Upvotes: 2

NoDataFound
NoDataFound

Reputation: 11959

This has more to do with JLS: https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.4.7 than with assert:

If a method is declared to have a return type (§8.4.5), then a compile-time error occurs if the body of the method can complete normally (§14.1).

In other words, a method with a return type must return only by using a return statement that provides a value return; the method is not allowed to "drop off the end of its body". See §14.17 for the precise rules about return statements in a method body.

Since your method must return a value (not void), only return or throw return the execution flow to the caller.

This rule ensure you avoid such case (this is a C example):

#define N = 10;
const char* CHAR_MAP[N] = ...;
const char  CHAR_VALUE[N] = ...;

char charOf(const char* s) {
  for (int i = 0; i < N; ++i) {
    if (strcmp(s, CHAR_MAP[i]) == 0) {
      return CHAR_VALUE[i];
    }
  }
  // not return value
}

My C is a bit rusty, this might not compile but the point is: in C - at least in C99 - the value returned in this case is undefined which may lead to several nasty problems, especially with pointers.

The rule in Java ensure that you always:

  1. Return a value, eg: null.
  2. Throw an exception, meaning an error.

This does not means the returned value will make thing works: returning null here may produce NullPointerException.

Beside, on a side note:

public Character charOf(String s) {
    for (Map.Entry<Character,String> entry : this.charMap.entrySet()) {
        if (s.equals(entry.getValue()) {
          return entry.getKey();
        }
    }
    throw new IllegalArgumentException("There is no mapping for \"" + s + "\"");
}

You should avoid mixing keySet() and get() when you can use entrySet().

Upvotes: 1

Oli
Oli

Reputation: 2602

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.

Upvotes: 4

Related Questions