JDK-8054172 : 14.11: switch runtime behavior doesn't account for reference types
  • Type: Bug
  • Component: specification
  • Sub-Component: language
  • Affected Version: 8
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2014-08-01
  • Updated: 2018-08-03
  • Resolved: 2016-09-27
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9Fixed
Related Reports
Duplicate :  
Relates :  
Description
The specification of 'switch' behavior at runtime does not properly handle Strings and enums.

"Otherwise, if the result is of a reference type, it is subject to unboxing conversion (��5.1.8)."

There is no unboxing conversion that can be applied to Strings or enums.

In the case of a boxed number, it may make more sense to box the case constants than to unbox the value, since the compile-time check was that the case constant was assignment-compatible with the wrapper class.  (This is just a matter of presentation -- there's no observable difference, since we've already checked for null.)

"If one of the case constants is equal to the value of the expression,then we say that the case label matches."

Primitives have a well-defined notion of equality (except perhaps floating-points, which aren't applicable here).  Reference types do not: this may be reasonably interpreted as either "val == const" or "val.equals(const)".  We should be explicit.

(Aside: for primitives, a subtle problem here is that we don't explicitly assert that a conversion takes place.  (E.g., the integer constant '1' is assignment-compatible with type byte, but the spec doesn't explicitly say that, at runtime, '1' is narrowed to a byte.)  I'm not sure we're consistent about this everywhere, though -- compare 14.17 and 15.26.1 -- so it's a minor problem.)
Comments
With regard to a case constant being "equal" to the value of the expression, it's important not to restrict implementers' freedom to detect equality in "interesting" ways. That said, we plainly do not want Strings being compared via ==, so this seems inoffensive: Equality is defined in terms of the == operator (15.21) unless the value of the expression is a String, in which case equality is defined in terms of the String.equals method.
17-09-2015

In starting to exempt some reference types from unboxing conversion, I pondered refactoring the paragraph: "When the switch statement is executed, first the Expression is evaluated. If the Expression evaluates to null, a NullPointerException is thrown and the entire switch statement completes abruptly for that reason. Otherwise, if the result is of a reference type, it is subject to unboxing conversion." along the cleaner lines of the assert, while, and do statements: 1. A FOO statement is executed by first evaluating the Expression. If the result is of type Boolean, it is subject to unboxing conversion. 2. If evaluation of the Expression or the subsequent unboxing conversion (if any) completes abruptly for some reason, the FOO statement completes abruptly for the same reason. Notice how (1) gives a null check for free in the unboxing conversion. The equivalent for switch would be: 1. A switch statement is executed by first evaluating the Expression. 1a. If the result is of type String or enum, and is null, then a NullPointerException is thrown and the entire switch statement completes abruptly for that reason. 1b. If the result is of type Character, Byte, Short, or Integer, it is subject to unboxing conversion. 2. If evaluation of the Expression or the subsequent unboxing conversion (if any) completes abruptly ... But what is the type of a null result? There's no ambiguity for assert, while, and do -- the answer can only be Boolean. For switch there's an ambiguity -- a null result could be of type String or an enum type (in which case trigger 1a) or a wrapper class (in which case trigger 1b). This ambiguity doesn't matter in practice, because the same effect occurs either way. (1a) explicitly throws NPE and the entire switch statement completes abruptly due to the NPE; (1b) implicitly throws NPE and (2) makes the entire switch statement complete abruptly due to the NPE. But it's undesirable to specify an ambiguous choice. The ambiguity could be avoided by examining the static type of Expression, but these are run-time rules and examining the dynamic type of the result is common throughout ch.14. In summary, switch must keep its explicit null check and defer type checking until a non-null result is known: 1. A switch statement is executed by first evaluating the Expression. 1a. If the Expression evaluates to null, a NullPointerException is thrown and the entire switch statement completes abruptly for that reason. 1b. Otherwise, if the result is of type Character, Byte, Short, or Integer, it is subject to unboxing conversion. 2. If evaluation of the Expression or the subsequent unboxing conversion (if any) completes abruptly ...
17-09-2015