Reputation: 158
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
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
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:
null
.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
Reputation: 2602
There are two reasons:
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.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