This specification is not final and is subject to change. Use is subject to license terms.

JEP 361: Switch Expressions

Changes to the Java® Language Specification • Version 14-internal+0-adhoc.gbierman.20190927

This document describes changes to the Java Language Specification to enhance the switch statement, including:

See JEP 361 for an overview.

Changes are described with respect to existing sections of the Java Language Specification. New text is indicated like this and deleted text is indicated like this. Explanation and discussion, as needed, is set aside in grey boxes.

Chapter 3: Lexical Structure

3.8 Identifiers

An identifier is an unlimited-length sequence of Java letters and Java digits, the first of which must be a Java letter.

Identifier:
IdentifierChars but not a Keyword or BooleanLiteral or NullLiteral
IdentifierChars:
JavaLetter {JavaLetterOrDigit}
JavaLetter:
any Unicode character that is a "Java letter"
JavaLetterOrDigit:
any Unicode character that is a "Java letter-or-digit"

A "Java letter" is a character for which the method Character.isJavaIdentifierStart(int) returns true.

A "Java letter-or-digit" is a character for which the method Character.isJavaIdentifierPart(int) returns true.

The "Java letters" include uppercase and lowercase ASCII Latin letters A-Z (\u0041-\u005a), and a-z (\u0061-\u007a), and, for historical reasons, the ASCII dollar sign ($, or \u0024) and underscore (_, or \u005f). The dollar sign should be used only in mechanically generated source code or, rarely, to access pre-existing names on legacy systems. The underscore may be used in identifiers formed of two or more characters, but it cannot be used as a one-character identifier due to being a keyword.

The "Java digits" include the ASCII digits 0-9 (\u0030-\u0039).

Letters and digits may be drawn from the entire Unicode character set, which supports most writing scripts in use in the world today, including the large sets for Chinese, Japanese, and Korean. This allows programmers to use identifiers in their programs that are written in their native languages.

An identifier cannot have the same spelling (Unicode character sequence) as a keyword (3.9), boolean literal (3.10.3), or the null literal (3.10.7), or a compile-time error occurs.

Two identifiers are the same only if, after ignoring characters that are ignorable, the identifiers have the same Unicode character for each letter or digit. An ignorable character is a character for which the method Character.isIdentifierIgnorable(int) returns true. Identifiers that have the same external appearance may yet be different.

For example, the identifiers consisting of the single letters LATIN CAPITAL LETTER A (A, \u0041), LATIN SMALL LETTER A (a, \u0061), GREEK CAPITAL LETTER ALPHA (A, \u0391), CYRILLIC SMALL LETTER A (a, \u0430) and MATHEMATICAL BOLD ITALIC SMALL A (a, \ud835\udc82) are all different.

Unicode composite characters are different from their canonical equivalent decomposed characters. For example, a LATIN CAPITAL LETTER A ACUTE (Á, \u00c1) is different from a LATIN CAPITAL LETTER A (A, \u0041) immediately followed by a NON-SPACING ACUTE (´, \u0301) in identifiers. See The Unicode Standard, Section 3.11 "Normalization Forms".

Examples of identifiers are:

The identifiers var and yield are restricted identifiers because they are not allowed in some contexts.

A type identifier is an identifier that is not the character sequence var , or the character sequence yield.

TypeIdentifier:
Identifier but not var or yield

Type identifiers are used in certain contexts involving the declaration or use of types. For example, the name of a class must be a TypeIdentifier, so it is illegal to declare a class named var or yield (8.1).

An unqualified method identifier is an identifier that is not the character sequence yield.

UnqualifiedMethodIdentifier:
Identifier but not yield

This restriction allows yield to be used in a yield statement (14.21) and still also be used as a (qualified) method name for compatibility reasons.

3.9 Keywords

51 character sequences, formed from ASCII letters, are reserved for use as keywords and cannot be used as identifiers (3.8).

Keyword:
(one of)
abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while
_ (underscore)

The keywords const and goto are reserved, even though they are not currently used. This may allow a Java compiler to produce better error messages if these C++ keywords incorrectly appear in programs.

A variety of character sequences are sometimes assumed, incorrectly, to be keywords:

  • The restricted identifiers var and yield are not keywords. var has special meaning as the type of a local variable declaration (14.4, 14.14.1, 14.14.2, 14.20.3) and the type of a lambda formal parameter (15.27.1). yield has special meaning in a yield statement (14.21). All invocations of a method called yield must be qualified so as to be distinguished from a yield statement.

A further ten character sequences are restricted keywords: open, module, requires, transitive, exports, opens, to, uses, provides, and with. These character sequences are tokenized as keywords solely where they appear as terminals in the ModuleDeclaration, ModuleDirective, and RequiresModifier productions (7.7). They are tokenized as identifiers everywhere else, for compatibility with programs written before the introduction of restricted keywords. There is one exception: immediately to the right of the character sequence requires in the ModuleDirective production, the character sequence transitive is tokenized as a keyword unless it is followed by a separator, in which case it is tokenized as an identifier.

Chapter 5: Conversions and Contexts

Every expression written in the Java programming language either produces no result (15.1) or has a type that can be deduced at compile time (15.3). When an expression appears in most contexts, it must be compatible with a type expected in that context; this type is called the target type. For convenience, compatibility of an expression with its surrounding context is facilitated in two ways:

If neither strategy is able to produce the appropriate type, a compile-time error occurs.

The rules determining whether an expression is a poly expression, and if so, its type and compatibility in a particular context, vary depending on the kind of context and the form of the expression. In addition to influencing the type of the expression, the target type may in some cases influence the run time behavior of the expression in order to produce a value of the appropriate type.

Similarly, the rules determining whether a target type allows an implicit conversion vary depending on the kind of context, the type of the expression, and, in one special case, the value of a constant expression (15.28 15.29). A conversion from type S to type T allows an expression of type S to be treated at compile time as if it had type T instead. In some cases this will require a corresponding action at run time to check the validity of the conversion or to translate the run-time value of the expression into a form appropriate for the new type T.

Example 5.0-1. Conversions at Compile Time and Run Time

The conversions possible in the Java programming language are grouped into several broad categories:

There are six kinds of conversion contexts in which poly expressions may be influenced by context or implicit conversions may occur. Each kind of context has different rules for poly expression typing and allows conversions in some of the categories above but not others. The contexts are:

The term "conversion" is also used to describe, without being specific, any conversions allowed in a particular context. For example, we say that an expression that is the initializer of a local variable is subject to "assignment conversion", meaning that a specific conversion will be implicitly chosen for that expression according to the rules for the assignment context.

Example 5.0-2. Conversions In Various Contexts

class Test {            
    public static void main(String[] args) {
        // Casting conversion (5.4) of a float literal to
        // type int. Without the cast operator, this would
        // be a compile-time error, because this is a
        // narrowing conversion (5.1.3):
        int i = (int)12.5f;

        // String conversion (5.4) of i's int value:
        System.out.println("(int)12.5f==" + i);

        // Assignment conversion (5.2) of i's value to type
        // float. This is a widening conversion (5.1.2):
        float f = i;

        // String conversion of f's float value:
        System.out.println("after float widening: " + f);

        // Numeric promotion (5.6) of i's value to type
        // float. This is a binary numeric promotion.
        // After promotion, the operation is float*float:
        System.out.print(f);
        f = f * i;

        // Two string conversions of i and f:
        System.out.println("*" + i + "==" + f);

        // Invocation conversion (5.3) of f's value
        // to type double, needed because the method Math.sin
        // accepts only a double argument:
        double d = Math.sin(f);

        // Two string conversions of f and d:
        System.out.println("Math.sin(" + f + ")==" + d);
    }
}

This program produces the output:

(int)12.5f==12
after float widening: 12.0
12.0*12==144.0
Math.sin(144.0)==-0.49102159389846934

5.6 Numeric Contexts

Numeric contexts apply to the operands of an arithmetic operator.

Numeric contexts allow the use of:

A numeric promotion is a process by which, given an arithmetic operator and its argument expressions, the arguments are converted to an inferred target type T. T is chosen during promotion such that each argument expression can be converted to T and the arithmetic operation is defined for values of type T.

The two kinds of numeric promotion are unary numeric promotion (5.6.1) and binary numeric promotion (5.6.2).

The entire section 5.6 has been re-organised and is replaced with the following text. (Sections 5.6.1 and 5.6.2 are now inlined in the section.)

Numeric contexts apply to the operands of arithmetic operators, array creation and access expressions, conditional expressions, and the result expressions of switch expressions.

An expression appears in a numeric context if it is one of:

  1. the dimension expression in an array creation expression (15.10.1)

  2. the index expression in an array access expression (15.10.3)

  3. the operand of a unary operator +, -, or ~

  4. an operand of a binary operator -, *, /, %, <, <=, >, or >=

  5. an operand of a shift operator <<, >>, or >>>. Operands to these shift operators are treated separately rather than as a group. A long shift distance (right operand) does not promote the value being shifted (left operand) to long.

  6. an operand of a binary operator +, ==, !=, &, ^, or |, where the operands are both convertible to a numeric type

  7. the second or third operand of a numeric conditional expression (15.25.2)

  8. a result expression of a standalone switch expression (15.28.1), where all the result expressions are convertible to a numeric type

The number of expressions in a numeric context depends on the context. Some contexts have exactly one expression, some contexts have exactly two, and some have an arbitrary number.

Numeric contexts are divided into three kinds:

Numeric promotion is a process that determines a promoted type for the expressions in a numeric context. This promoted type, T, is chosen such that each expression can be converted to T and, in the case of an arithmetic operation, the operation is defined for values of type T. The order of expressions in a numeric context is not significant in the process of numeric promotion.

Numeric promotion applies the following steps:

  1. If any expression is of a reference type, it is subjected to unboxing conversion (5.1.8).

  2. Next, widening primitive conversion (5.1.2) and narrowing primitive conversion (5.1.3) are applied to some expressions in the process of determining a promoted type, as specified by the following rules:

    • If any expression is of type double, then the promoted type is double, and other expressions that are not of type double undergo widening primitive conversion to double.

    • Otherwise, if any expression is of type float, then the promoted type is float and other expressions that are not of type float undergo widening primitive conversion to float.

    • Otherwise, if any expression is of type long, then the promoted type is long and other expressions that are not of type long undergo widening primitive conversion to long.

    • Otherwise, if any expression appears in a numeric array context or a numeric arithmetic context, rather than a numeric choice context, then the promoted type is int and other expressions that are not of type int undergo widening primitive conversion to int.

    • Otherwise, the expression is in a numeric choice context and the following rules apply:

      • If any expression is of type int and is not a constant expression, then T is int and other expressions that are not of type int undergo widening primitive conversion to int.

      • Otherwise, if any expression is of type short, and every other expression is either of type short, or of type byte, or a constant expression of type int with a value that is representable in the type short, then T is short, the byte expressions undergo widening primitive conversion to short, and the int expressions undergo narrowing primitive conversion to short.

      • Otherwise, if any expression is of type byte, and every other expression is either of type byte or a constant expression of type int with a value that is representable in the type byte, then T is byte and the int expressions undergo narrowing primitive conversion to byte.

      • Otherwise, if any expression is of type char, and every other expression is either of type char or a constant expression of type int with a value that is representable in the type char, then T is char and the int expressions undergo narrowing primitive conversion to char.

      • Otherwise, T is int and all the expressions that are not of type int undergo widening primitive conversion to int.

  3. After the conversion(s), if any, value set conversion (5.1.13) is then applied to each expression.

A unary numeric promotion applies numeric promotion to a single expression that occurs in a numeric context. A binary numeric promotion is performed on a pair of expressions that occur in a numeric context that is not a numeric choice context.

Example 5.6-1. Unary Numeric Promotion

class Test {
    public static void main(String[] args) {
        byte b = 2;
        int a[] = new int[b];  // dimension expression promotion
        char c = '\u0001';
        a[c] = 1;              // index expression promotion
        a[0] = -c;             // unary - promotion
        System.out.println("a: " + a[0] + "," + a[1]);
        b = -1;
        int i = ~b;            // bitwise complement promotion
        System.out.println("~0x" + Integer.toHexString(b)
                           + "==0x" + Integer.toHexString(i));
        i = b << 4L;           // shift promotion (left operand)
        System.out.println("0x" + Integer.toHexString(b)
                           + "<<4L==0x" + Integer.toHexString(i));
    }
}

This program produces the output:

a: -1,1
~0xffffffff==0x0
0xffffffff<<4L==0xfffffff0

Example 5.6-2. Binary Numeric Promotion

class Test {
    public static void main(String[] args) {
        int i    = 0;
        float f  = 1.0f;
        double d = 2.0;
        // First int*float is promoted to float*float, then
        // float==double is promoted to double==double:
        if (i * f == d) System.out.println("oops");

        // A char&byte is promoted to int&int:
        byte b = 0x1f;
        char c = 'G';
        int control = c & b;
        System.out.println(Integer.toHexString(control));

        // Here int:float is promoted to float:float:
        f = (b==0) ? i : 4.0f;
        System.out.println(1.0/f);
    }
}

This program produces the output:

7
0.25

The example converts the ASCII character G to the ASCII control-G (BEL), by masking off all but the low 5 bits of the character. The 7 is the numeric value of this control character.

5.6.1 Unary Numeric Promotion

Some operators apply unary numeric promotion to a single operand, which must produce a value of a numeric type:

After the conversion(s), if any, value set conversion (5.1.13) is then applied.

Unary numeric promotion is performed on expressions in the following situations:

Example 5.6.1-1. Unary Numeric Promotion

class Test {
    public static void main(String[] args) {
        byte b = 2;
        int a[] = new int[b];  // dimension expression promotion
        char c = '\u0001';
        a[c] = 1;              // index expression promotion
        a[0] = -c;             // unary - promotion
        System.out.println("a: " + a[0] + "," + a[1]);
        b = -1;
        int i = ~b;            // bitwise complement promotion
        System.out.println("~0x" + Integer.toHexString(b)
                           + "==0x" + Integer.toHexString(i));
        i = b << 4L;           // shift promotion (left operand)
        System.out.println("0x" + Integer.toHexString(b)
                           + "<<4L==0x" + Integer.toHexString(i));
    }
}

This program produces the output:

a: -1,1
~0xffffffff==0x0
0xffffffff<<4L==0xfffffff0

5.6.2 Binary Numeric Promotion

When an operator applies binary numeric promotion to a pair of operands, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:

  1. If any operand is of a reference type, it is subjected to unboxing conversion (5.1.8).

  2. Widening primitive conversion (5.1.2) is applied to convert either or both operands as specified by the following rules:

    • If either operand is of type double, the other is converted to double.

    • Otherwise, if either operand is of type float, the other is converted to float.

    • Otherwise, if either operand is of type long, the other is converted to long.

    • Otherwise, both operands are converted to type int.

After the conversion(s), if any, value set conversion (5.1.13) is then applied to each operand.

Binary numeric promotion is performed on the operands of certain operators:

Example 5.6.2-1. Binary Numeric Promotion

class Test {
    public static void main(String[] args) {
        int i    = 0;
        float f  = 1.0f;
        double d = 2.0;
        // First int*float is promoted to float*float, then
        // float==double is promoted to double==double:
        if (i * f == d) System.out.println("oops");

        // A char&byte is promoted to int&int:
        byte b = 0x1f;
        char c = 'G';
        int control = c & b;
        System.out.println(Integer.toHexString(control));

        // Here int:float is promoted to float:float:
        f = (b==0) ? i : 4.0f;
        System.out.println(1.0/f);
    }
}

This program produces the output:

7
0.25

The example converts the ASCII character G to the ASCII control-G (BEL), by masking off all but the low 5 bits of the character. The 7 is the numeric value of this control character.

Chapter 6: Names

6.5 Determining the Meaning of a Name

The meaning of a name depends on the context in which it is used. The determination of the meaning of a name requires three steps:

ModuleName:
Identifier
ModuleName . Identifier
PackageName:
Identifier
PackageName . Identifier
TypeName:
TypeIdentifier
PackageOrTypeName . TypeIdentifier
PackageOrTypeName:
Identifier
PackageOrTypeName . Identifier
ExpressionName:
Identifier
AmbiguousName . Identifier
MethodName:
Identifier UnqualifiedMethodIdentifier
AmbiguousName:
Identifier
AmbiguousName . Identifier

The use of context helps to minimize name conflicts between entities of different kinds. Such conflicts will be rare if the naming conventions described in 6.1 are followed. Nevertheless, conflicts may arise unintentionally as types developed by different programmers or different organizations evolve. For example, types, methods, and fields may have the same name. It is always possible to distinguish between a method and a field with the same name, since the context of a use always tells whether a method is intended.

6.5.2 Reclassification of Contextually Ambiguous Names

An AmbiguousName is then reclassified as follows.

If the AmbiguousName is a simple name, consisting of a single Identifier:

If the AmbiguousName is a qualified name, consisting of a name, a ".", and an Identifier, then the name to the left of the "." is first reclassified, for it is itself an AmbiguousName. There is then a choice:

The requirement that a potential type name be "a valid TypeIdentifier" prevents treating var and yield as a type name. It is usually redundant, because the rules for declarations already prevent the introduction of types named var and yield. However, in some cases, a compiler may find a binary class named var or yield, and we want to be clear that such classes can never be named. The simplest solution is to consistently check for a valid TypeIdentifier.

Example 6.5.2-1. Reclassification of Contextually Ambiguous Names

Consider the following contrived "library code":

package org.rpgpoet;
import java.util.Random;
public interface Music { Random[] wizards = new Random[4]; }

and then consider this example code in another package:

package bazola;
class Gabriel {
    static int n = org.rpgpoet.Music.wizards.length;
}

First of all, the name org.rpgpoet.Music.wizards.length is classified as an ExpressionName because it functions as a PostfixExpression. Therefore, each of the names:

org.rpgpoet.Music.wizards
org.rpgpoet.Music
org.rpgpoet
org

is initially classified as an AmbiguousName. These are then reclassified:

Chapter 11: Exceptions

11.2 Compile-Time Checking of Exceptions

11.2.1 Exception Analysis of Expressions

A class instance creation expression (15.9) can throw an exception class E iff either:

A method invocation expression (15.12) can throw an exception class E iff either:

A switch expression (15.28) can throw an exception class E iff either:

A lambda expression (15.27) can throw no exception classes.

For every other kind of expression, the expression can throw an exception class E iff one of its immediate subexpressions can throw E.

Note that a method reference expression (15.13) of the form Primary :: [TypeArguments] Identifier can throw an exception class if the Primary subexpression can throw an exception class. In contrast, a lambda expression can throw nothing, and has no immediate subexpressions on which to perform exception analysis. It is the body of a lambda expression, containing expressions and statements, that can throw exception classes.

11.2.2 Exception Analysis of Statements

A throw statement (14.18) whose thrown expression has static type E and is not a final or effectively final exception parameter can throw E or any exception class that the thrown expression can throw.

For example, the statement throw new java.io.FileNotFoundException(); can throw java.io.FileNotFoundException only. Formally, it is not the case that it "can throw" a subclass or superclass of java.io.FileNotFoundException.

A throw statement whose thrown expression is a final or effectively final exception parameter of a catch clause C can throw an exception class E iff:

A try statement (14.20) can throw an exception class E iff either:

An explicit constructor invocation statement (8.8.7.1) can throw an exception class E iff either:

A switch statement (14.11) can throw an exception class E iff either:

Any other statement S can throw an exception class E iff an expression or statement immediately contained in S can throw E.

Chapter 14: Blocks and Statements

14.1 Normal and Abrupt Completion of Statements

Every statement has a normal mode of execution in which certain computational steps are carried out. The following sections describe the normal mode of execution for each kind of statement.

If all the steps are carried out as described, with no indication of abrupt completion, the statement is said to complete normally. However, certain events may prevent a statement from completing normally:

If such an event occurs, then execution of one or more statements may be terminated before all steps of their normal mode of execution have completed; such statements are said to complete abruptly.

An abrupt completion always has an associated reason, which is one of the following:

The terms "complete normally" and "complete abruptly" also apply to the evaluation of expressions (15.6). The only reason an expression can complete abruptly is that an exception is thrown, because of either a throw with a given value (14.18) or a run-time exception or error (11, 15.6).

If a statement evaluates an expression, abrupt completion of the expression always causes the immediate abrupt completion of the statement, with the same reason. All succeeding steps in the normal mode of execution are not performed.

Unless otherwise specified in this chapter, abrupt completion of a substatement causes the immediate abrupt completion of the statement itself, with the same reason, and all succeeding steps in the normal mode of execution of the statement are not performed.

Unless otherwise specified, a statement completes normally if all expressions it evaluates and all substatements it executes complete normally.

14.5 Statements

There are many kinds of statements in the Java programming language. Most correspond to statements in the C and C++ languages, but some are unique.

As in C and C++, the if statement of the Java programming language suffers from the so-called "dangling else problem," illustrated by this misleadingly formatted example:

if (door.isOpen())
    if (resident.isVisible())
        resident.greet("Hello!");
else door.bell.ring();  // A "dangling else"

The problem is that both the outer if statement and the inner if statement might conceivably own the else clause. In this example, one might surmise that the programmer intended the else clause to belong to the outer if statement.

The Java programming language, like C and C++ and many programming languages before them, arbitrarily decrees that an else clause belongs to the innermost if to which it might possibly belong. This rule is captured by the following grammar:

Statement:
StatementWithoutTrailingSubstatement
LabeledStatement
IfThenStatement
IfThenElseStatement
WhileStatement
ForStatement
StatementNoShortIf:
StatementWithoutTrailingSubstatement
LabeledStatementNoShortIf
IfThenElseStatementNoShortIf
WhileStatementNoShortIf
ForStatementNoShortIf
StatementWithoutTrailingSubstatement:
Block
EmptyStatement
ExpressionStatement
AssertStatement
SwitchStatement
DoStatement
BreakStatement
ContinueStatement
ReturnStatement
SynchronizedStatement
ThrowStatement
TryStatement
YieldStatement

The following productions from [14.9] are shown here for convenience:

IfThenStatement:
if ( Expression ) Statement
IfThenElseStatement:
if ( Expression ) StatementNoShortIf else Statement
IfThenElseStatementNoShortIf:
if ( Expression ) StatementNoShortIf else StatementNoShortIf

Statements are thus grammatically divided into two categories: those that might end in an if statement that has no else clause (a "short if statement") and those that definitely do not.

Only statements that definitely do not end in a short if statement may appear as an immediate substatement before the keyword else in an if statement that does have an else clause.

This simple rule prevents the "dangling else" problem. The execution behavior of a statement with the "no short if" restriction is identical to the execution behavior of the same kind of statement without the "no short if" restriction; the distinction is drawn purely to resolve the syntactic difficulty.

14.11 The switch Statement

Much of this section has been moved into subsections.

The switch statement transfers control to one of several statements depending on the value of an expression. This expression is known as the selector expression.

SwitchStatement:
switch ( Expression ) SwitchBlock
SwitchBlock:
{ {SwitchBlockStatementGroup} {SwitchLabel} }
SwitchBlockStatementGroup:
SwitchLabels BlockStatements
SwitchLabels:
SwitchLabel {SwitchLabel}
SwitchLabel:
case ConstantExpression :
case EnumConstantName :
default :
EnumConstantName:
Identifier

The type of the Expression must be char, byte, short, int, Character, Byte, Short, Integer, String, or an enum type (8.9), or a compile-time error occurs.

14.11.1 The Switch Block

SwitchBlock:
{ SwitchRule {SwitchRule} }
{ {SwitchLabeledStatementGroup} {SwitchLabel :} }
SwitchLabeledRule:
SwitchLabel -> Expression ;
SwitchLabel -> Block
SwitchLabel -> ThrowStatement
SwitchLabel:
case CaseConstant {, CaseConstant}
default
CaseConstant:
ConditionalExpression
SwitchLabeledStatementGroup:
SwitchLabel : {SwitchLabel :} BlockStatements

The body of both a switch statement and a switch expression (15.28) is known as a switch block. This block can consist of either:

  1. Switch rules, which use -> to introduce either a switch rule expression, a switch rule block, or a switch rule throw statement; or

  2. Switch labeled statement groups, which use : to introduce switch labeled block statements.

Any statement immediately contained by the switch block may be labeled with one or more switch labels, which are case or default labels. A switch rule has a switch label and either an expression, a block, or a throw statement. A switch labeled statement group has one or more switch labels and zero or more block statements. A switch label is either a case or default label. Every case label has a one or more case constants, each of which is either a constant expression or the name of an enum constant. Switch labels and their case constants are said to be associated with the switch statement block.

Every case constant associated with a given switch block must be either a constant expression (15.29) or the name of an enum constant ([8.9.1]), or a compile-time error occurs.

This excludes the possibility of using null as a case constant.

Given a switch statement, all of the following must be true or a compile-time error occurs:

A switch block is said to be compatible with the type of the selector expression, T, if both of the following are true:

At run time, the value of the selector expression is compared with each case constant (if the selector expression is a reference type that is convertible to a numeric type, then it is first subject to an unboxing conversion (5.1.8)):

A case label can contain several case constants, and matches the value of a selector expression if any one of its case constants matches.

switch (day) {
    case SATURDAY, SUNDAY:
        // matches if it is a Saturday OR a Sunday
        System.out.println("It's the weekend!");
}

The prohibition against using null as a case constant prevents code being written that can never be executed. If the switch statement's Expression selector expression is of a reference type, that is, String or a boxed primitive type or an enum type, then an exception will be thrown will occur if the Expression evaluates to null at run time. In the judgment of the designers of the Java programming language, this is a better outcome than silently skipping the entire switch statement or choosing to execute the statements (if any) after the default label (if any) either the switch block not matching or even the default label matching.

A Java compiler is encouraged (but not required) to provide a warning if a switch on an enum-valued expression lacks a default label and lacks case labels for one or more of the enum's constants. Such a switch will silently do nothing if the expression evaluates to one of the missing constants.

In C and C++ the body of a switch statement can be a statement and statements with case labels heads do not have to be immediately contained by that statement. Consider the simple loop:

for (i = 0; i < n; ++i) foo();

where n is known to be positive. A trick known as Duff's device can be used in C or C++ to unroll the loop, but this is not valid code in the Java programming language:

int q = (n+7)/8;
switch (n%8) {
    case 0: do { foo();    // Great C hack, Tom,
    case 7:      foo();    // but it's not valid here.
    case 6:      foo();
    case 5:      foo();
    case 4:      foo();
    case 3:      foo();
    case 2:      foo();
    case 1:      foo();
            } while (--q > 0);
}

Fortunately, this trick does not seem to be widely known or used. Moreover, it is less needed nowadays; this sort of code transformation is properly in the province of state-of-the-art optimizing compilers.

14.11.2 The switch Block of a switch Statement

Given a switch statement, all of the following must be true or a compile-time error occurs:

Switch rules in switch statements differ from those in switch expressions (15.28). In switch statements, any switch rule expression must be a statement expression (14.8), whereas in switch expressions there is no such restriction (15.28.1).

14.11.3 Execution of a switch statement

A switch statement is executed by first evaluating the Expression. 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 type Character, Byte, Short, or Integer, it is subject to unboxing conversion (5.1.8).

If evaluation of the Expression or the subsequent unboxing conversion (if any) completes abruptly for some reason, the switch statement completes abruptly for the same reason.

Otherwise, execution continues by comparing the value of the Expression with each case constant, and there is a choice:

When the switch statement is executed, first the selector expression is evaluated:

  1. If evaluation of the selector expression completes abruptly for some reason, the switch statement completes abruptly for the same reason.

  2. If the selector expression evaluates to null, then a NullPointerException is thrown and the entire switch statement completes abruptly for that reason.

  3. Otherwise, execution continues by determining if a switch label associated with the switch block matches the value of the selector expression:

    If no switch label matches, then the entire switch statement completes normally.

    If a switch label matches, then one of the following applies:

    • If it is the switch label for a switch rule statement expression, then the statement expression is evaluated. If the result of evaluation is a value, it is discarded. If the evaluation completes normally, then the switch statement completes normally.

    • If it is the switch label for a switch rule block, then the block is executed. If this block completes normally, then the switch statement completes normally.

    • If it is the switch label for a switch rule throw statement, then the throw statement is executed.

    • If it is the switch label for a statement group, then all the statements in the switch block that follow it are executed in order. If these statements complete normally, then the switch statement completes normally.

    • Otherwise, there are no statements that follow it in the switch block, and the switch statement completes normally.

If any statement immediately contained by the Block body of the switch statement execution of any statement or expression completes abruptly, it is handled as follows:

The case of abrupt completion because of a yield statement is handled by the general rule for switch expressions (15.28).

The case of abrupt completion because of a break with a label is handled by the general rule for labeled statements (14.7).

Example 14.11-1 14.11.3-1. Fall-Through in the switch Statement

As in C and C++, execution of statements in a switch block "falls through labels."

When a selector expression matches a switch label for a switch rule, the switch rule expression or statement introduced by the switch label is executed and nothing else. In the case of a switch label for a statement group, all the block statements in the switch block that follow the switch label are executed, including those that appear after subsequent switch labels. The effect is that, as in C and C++, execution of statements can "fall through labels."

For example, the program:

class TooMany {
    static void howMany(int k) {
        switch (k) {
            case 1: System.out.print("one ");
            case 2: System.out.print("too ");
            case 3: System.out.println("many");
        }
    }
    public static void main(String[] args) {
        howMany(3);
        howMany(2);
        howMany(1);
    }
}

contains a switch block in which the code for each case falls through into the code for the next case. As a result, the program prints:

many
too many
one too many

This fall through behaviour can be the cause of subtle bugs. If code is not to fall through case to case in this manner, then break statements should be used, as in this example switch rules can be used, or break statements can be used to explicitly indicate where control should be transfered, as follows:

class TwoMany {
    static void howMany(int k) {
        switch (k) {
            case 1: System.out.println("one");
                    break;  // exit the switch
            case 2: System.out.println("two");
                    break;  // exit the switch
            case 3: System.out.println("many");
                    break;  // not needed, but good style
        }
    }
    public static void main(String[] args) {
        howMany(1);
        howMany(2);
        howMany(3);
    }
}
class TwoMany {
    static void howManyRule(int k) {
        switch (k) {
            case 1 -> System.out.println("one");
            case 2 -> System.out.println("two");
            case 3 -> System.out.println("many");
        }
    }
    static void howManyGroup(int k) {
        switch (k) {
            case 1: System.out.println("one");
                    break;  // exit the switch
            case 2: System.out.println("two");
                    break;  // exit the switch
            case 3: System.out.println("many");
                    break;  // not needed, but good style
        }
    }
    public static void main(String args) {
        howManyRule(1);
        howManyRule(2);
        howManyRule(3);
        howManyGroup(1);
        howManyGroup(2);
        howManyGroup(3);
    }
}

This program prints:

one
two
many
one
two
many
one
two
many

14.15 The break Statement

A break statement transfers control out of an enclosing statement.

BreakStatement:
break [ Identifier ] ;
break Identifier ;
break ;

There are two kinds of break statement:

  1. A break statement with label Identifier.

  2. A break statement with no label.

A break statement with no label attempts to transfer control to the innermost enclosing switch, while, do, or for statement of the immediately enclosing method or initializer; this statement, which is called the break target, then immediately completes normally.

To be precise, a break statement with no label always completes abruptly, the reason being a break with no label.

If no switch, while, do, or for statement in the immediately enclosing method, constructor, or initializer contains the break statement, a compile-time error occurs.

A break statement with label Identifier attempts to transfer control to the enclosing labeled statement (14.7) that has the same Identifier as its label; this statement, which is called the break target, then immediately completes normally. In this case, the break target need not be a switch, while, do, or for statement.

To be precise, a break statement with label Identifier always completes abruptly, the reason being a break with label Identifier.

A break statement must refer to a label within the immediately enclosing method, constructor, initializer, or lambda body. There are no non-local jumps. If no labeled statement with Identifier as its label in the immediately enclosing method, constructor, initializer, or lambda body contains the break statement, a compile-time error occurs.

It is a compile-time error if a break statement has no break target.

It is a compile-time error if the break target of a break statement contains any method, constructor, initializer, lambda expression, or switch expression that encloses the break statement.

Execution of an break statement with no label always completes abruptly, the reason being a break with no label.

Execution of a break statement with label Identifier always completes abruptly, the reason being a break with label Identifier.

It can be seen, then, that a break statement always completes abruptly.

The preceding descriptions say "attempts to transfer control" rather than just "transfers control" because if there are any try statements (14.20) within the break target whose try blocks or catch clauses contain the break statement, then any finally clauses of those try statements are executed, in order, innermost to outermost, before control is transferred to the break target. Abrupt completion of a finally clause can disrupt the transfer of control initiated by a break statement.

Example 14.15-1. The break Statement

In the following example, a mathematical graph is represented by an array of arrays. A graph consists of a set of nodes and a set of edges; each edge is an arrow that points from some node to some other node, or from a node to itself. In this example it is assumed that there are no redundant edges; that is, for any two nodes P and Q, where Q may be the same as P, there is at most one edge from P to Q.

Nodes are represented by integers, and there is an edge from node i to node edges[*i*][*j*] for every i and j for which the array reference edges[*i*][*j*] does not throw an ArrayIndexOutOfBoundsException.

The task of the method loseEdges, given integers i and j, is to construct a new graph by copying a given graph but omitting the edge from node i to node j, if any, and the edge from node j to node i, if any:

class Graph {
    int edges[][];
    public Graph(int[][] edges) { this.edges = edges; }

    public Graph loseEdges(int i, int j) {
        int n = edges.length;
        int[][] newedges = new int[n][];
        for (int k = 0; k < n; ++k) {
edgelist:
{
            int z;
search:
{
            if (k == i) {
                for (z = 0; z < edges[k].length; ++z) {
                    if (edges[k][z] == j) break search;
                }
            } else if (k == j) {
                for (z = 0; z < edges[k].length; ++z) {
                    if (edges[k][z] == i) break search;
                }
            }

            // No edge to be deleted; share this list.
            newedges[k] = edges[k];
            break edgelist;
} //search

            // Copy the list, omitting the edge at position z.
            int m = edges[k].length - 1;
            int ne[] = new int[m];
            System.arraycopy(edges[k], 0, ne, 0, z);
            System.arraycopy(edges[k], z+1, ne, z, m-z);
            newedges[k] = ne;
} //edgelist
        }
        return new Graph(newedges);
    }
}

Note the use of two statement labels, edgelist and search, and the use of break statements. This allows the code that copies a list, omitting one edge, to be shared between two separate tests, the test for an edge from node i to node j, and the test for an edge from node j to node i.

14.16 The continue Statement

A continue statement may occur only in a while, do, or for statement; statements of these three kinds are called iteration statements. Control passes to the loop-continuation point of an iteration statement.

ContinueStatement:
continue [ Identifier ] ;
continue Identifier ;
continue ;

There are two kinds of continue statement:

  1. A continue statement with label Identifier.

  2. A continue statement with no label.

A continue statement with no label attempts to transfer control to the innermost enclosing while, do, or for statement of the immediately enclosing method, constructor, or initializer; this statement, which is called the continue target, then immediately ends the current iteration and begins a new one.

To be precise, such a continue statement always completes abruptly, the reason being a continue with no label.

If no while, do, or for statement of the immediately enclosing method, constructor, or initializer contains the continue statement, a compile-time error occurs.

A continue statement with label Identifier attempts to transfer control to the enclosing labeled statement (14.7) that has the same Identifier as its label; that statement, which is called the continue target, then immediately ends the current iteration and begins a new one.

To be precise, a continue statement with label Identifier always completes abruptly, the reason being a continue with label Identifier.

The continue target of a continue statement with label must be a while, do, or for statement, or a compile-time error occurs.

A continue statement must refer to a label within the immediately enclosing method, constructor, initializer, or lambda body. There are no non-local jumps. If no labeled statement with Identifier as its label in the immediately enclosing method, constructor, initializer, or lambda body contains the continue statement, a compile-time error occurs.

It is a compile-time error if a continue statement has no continue target.

It is a compile-time error if the continue target contains any method, constructor, initializer, lambda expression, or switch expression that contains the continue statement.

Execution of an continue statement with no label always completes abruptly, the reason being a continue with no label.

Execution of a continue statement with label Identifier always completes abruptly, the reason being a continue with label Identifier.

It can be seen, then, that a continue statement always completes abruptly.

See the descriptions of the while statement (14.12), do statement (14.13), and for statement (14.14) for a discussion of the handling of abrupt termination because of continue.

The preceding descriptions say "attempts to transfer control" rather than just "transfers control" because if there are any try statements (14.20) within the continue target whose try blocks or catch clauses contain the continue statement, then any finally clauses of those try statements are executed, in order, innermost to outermost, before control is transferred to the continue target. Abrupt completion of a finally clause can disrupt the transfer of control initiated by a continue statement.

Example 14.16-1. The continue Statement

In the Graph class in 14.15, one of the break statements is used to finish execution of the entire body of the outermost for loop. This break can be replaced by a continue if the for loop itself is labeled:

class Graph {
    int edges[][];
    public Graph(int[][] edges) { this.edges = edges; }

    public Graph loseEdges(int i, int j) {
        int n = edges.length;
        int[][] newedges = new int[n][];
edgelists:
        for (int k = 0; k < n; ++k) {
            int z;
search:
{
            if (k == i) {
                for (z = 0; z < edges[k].length; ++z) {
                    if (edges[k][z] == j) break search;
                }
            } else if (k == j) {
                for (z = 0; z < edges[k].length; ++z) {
                    if (edges[k][z] == i) break search;
                }
            }

            // No edge to be deleted; share this list.
            newedges[k] = edges[k];
            continue edgelists;
} //search

            // Copy the list, omitting the edge at position z.
            int m = edges[k].length - 1;
            int ne[] = new int[m];
            System.arraycopy(edges[k], 0, ne, 0, z);
            System.arraycopy(edges[k], z+1, ne, z, m-z);
            newedges[k] = ne;
        } //edgelists
        return new Graph(newedges);
    }
}

Which to use, if either, is largely a matter of programming style.

14.17 The return Statement

A return statement returns control to the invoker of a method (8.4, 15.12) or constructor (8.8, 15.9).

ReturnStatement:
return [ Expression ] ;
return Exression ;
return ;

There are two kinds of return statement:

  1. A return statement with value Expression.

  2. A return statement with no value.

A return statement is contained in the innermost constructor, method, initializer, or lambda expression whose body encloses the return statement.

A return statement attempts to transfer control to the invoker of the innermost enclosing constructor, method, or lambda expression; this declaration is called the return target. In the case of a return statement with value Expression, the value of the Expression becomes the value of the invocation.

It is a compile-time error if a return statement has no return target.

It is a compile-time error if the return target contains any initializer, or switch expression that encloses the return statement.

It is a compile-time error if a return statement is contained in an instance initializer or a static initializer (8.6, 8.7).

A return statement with no Expression must be contained in one of the following, or a compile-time error occurs:

It is a compile-time error if the return target of a return statement with no value is a method, and that method is not declared 'void'.

A return statement with no Expression attempts to transfer control to the invoker of the method, constructor, or lambda body that contains it. To be precise, a return statement with no Expression always completes abruptly, the reason being a return with no value.

A return statement with an Expression must be contained in one of the following, or a compile-time error occurs:

The Expression must denote a variable or a value, or a compile-time error occurs.

When a return statement with an Expression appears in a method declaration, the Expression must be assignable (5.2) to the declared return type of the method, or a compile-time error occurs.

It is a compile-time error if the return target of a return statement with an Expression is a constructor or a method that is declared void.

It is a compile-time error if the return target of a return statement with an Expression is a method with declared return type T, and Expression is not assignable (5.2) to T.

Execution of a return statement with no value always completes abruptly, the reason being a return with no value.

A return statement with an Expression attempts to transfer control to the invoker of the method or lambda body that contains it; the value of the Expression becomes the value of the method invocation. More precisely, execution of such a return statement first evaluates the Expression. Execution of a return statement with an Expression first evaluates the Expression. If the evaluation of the Expression completes abruptly for some reason, then the return statement completes abruptly for that reason. If evaluation of the Expression completes normally, producing a value V, then the return statement completes abruptly, the reason being a return with value V.

If the expression is of type float and is not FP-strict (15.4), then the value may be an element of either the float value set or the float-extended-exponent value set (4.2.3). If the expression is of type double and is not FP-strict, then the value may be an element of either the double value set or the double-extended-exponent value set.

It can be seen, then, that a return statement always completes abruptly.

The preceding descriptions say "attempts to transfer control" rather than just "transfers control" because if there are any try statements (14.20) within the method or constructor whose try blocks or catch clauses contain the return statement, then any finally clauses of those try statements will be executed, in order, innermost to outermost, before control is transferred to the invoker of the method or constructor. Abrupt completion of a finally clause can disrupt the transfer of control initiated by a return statement.

14.21 The yield Statement

This replaces the current section 14.21, which is renumbered as 14.22.

A yield statement transfers control by causing an enclosing switch expression to produce a specified value.

YieldStatement:
yield Expression ;

A yield statement attempts to transfer control to the innermost enclosing switch expression; this expression, which is called the yield target, then immediately completes normally and the value of the Expression becomes the value of the switch expression.

It is a compile-time error if a yield statement has no yield target.

It is a compile-time error if the yield target contains any method, constructor, initializer, or lambda expression that encloses the yield statement.

It is a compile-time error if the Expression of a yield statement is void (15.1).

Execution of a yield statement first evaluates the Expression. If the evaluation of the Expression completes abruptly for some reason, then the yield statement completes abruptly for that reason. If evaluation of the Expression completes normally, producing a value V, then the yield statement completes abruptly, the reason being a yield with value V.

It can be seen that, like a break statement, a yield statement always completes abruptly.

Example 14.15-2. The yield Statement

In the following example, a yield statement is used to produce a value for the enclosing switch expression.

class Enum {
    enum Day {
        MONDAY,
        TUESDAY,
        WEDNESDAY,
        THURSDAY,
        FRIDAY,
        SATURDAY,
        SUNDAY
    }

    public static int calculate(Day d) {
        return switch (d) {
            case MONDAY  -> 0;
            case TUESDAY -> 1;
            default      -> {
                int l = d.toString().length();
                yield l*l;               // Value of switch expression
            }
        };
    }
}

14.21 14.22 Unreachable Statements

This section has been renumbered from 14.21 to 14.22.

It is a compile-time error if a statement cannot be executed because it is unreachable.

This section is devoted to a precise explanation of the word "reachable." The idea is that there must be some possible execution path from the beginning of the constructor, method, instance initializer, or static initializer that contains the statement to the statement itself. The analysis takes into account the structure of statements. Except for the special treatment of while, do, and for statements whose condition expression has the constant value true, the values of expressions are not taken into account in the flow analysis.

For example, a Java compiler will accept the code:

{
    int n = 5;
    while (n > 7) k = 2;
}

even though the value of n is known at compile time and in principle it can be known at compile time that the assignment to k can never be executed.

The rules in this section define two technical terms:

The definitions here allow a statement to complete normally only if it is reachable.

To shorten the description of the rules, the customary abbreviation "iff" is used to mean "if and only if."

A reachable break statement exits a statement if, within the break target, either there are no try statements whose try blocks contain the break statement, or there are try statements whose try blocks contain the break statement and all finally clauses of those try statements can complete normally.

This definition is based on the logic around "attempts to transfer control" in 14.15.

A continue statement continues a do statement if, within the do statement, either there are no try statements whose try blocks contain the continue statement, or there are try statements whose try blocks contain the continue statement and all finally clauses of those try statements can complete normally.

The rules are as follows:

One might expect the if statement to be handled in the following manner:

This approach would be consistent with the treatment of other control structures. However, in order to allow the if statement to be used conveniently for "conditional compilation" purposes, the actual rules differ.

As an example, the following statement results in a compile-time error:

while (false) { x=3; }

because the statement x=3; is not reachable; but the superficially similar case:

if (false) { x=3; }

does not result in a compile-time error. An optimizing compiler may realize that the statement x=3; will never be executed and may choose to omit the code for that statement from the generated class file, but the statement x=3; is not regarded as "unreachable" in the technical sense specified here.

The rationale for this differing treatment is to allow programmers to define "flag" variables such as:

static final boolean DEBUG = false;

and then write code such as:

if (DEBUG) { x=3; }

The idea is that it should be possible to change the value of DEBUG from false to true or from true to false and then compile the code correctly with no other changes to the program text.

Conditional compilation comes with a caveat. If a set of classes that use a "flag" variable - or more precisely, any static constant variable (4.12.4) - are compiled and conditional code is omitted, it does not suffice later to distribute just a new version of the class or interface that contains the definition of the flag. The classes that use the flag will not see its new value, so their behavior may be surprising. In essence, a change to the value of a flag is binary compatible with pre-existing binaries (no LinkageError occurs) but not behaviorally compatible.

Another reason for "inlining" values of static constant variables is because of switch statements. They are the only kind of statement that relies on constant expressions, namely that each case label of a switch statement must be a constant expression whose value is different than every other case label. case labels are often references to static constant variables so it may not be immediately obvious that all the labels have different values. If it is proven that there are no duplicate labels at compile time, then inlining the values into the class file ensures there are no duplicate labels at run time either - a very desirable property.

Example 14.21-1. Conditional Compilation

If the example:

class Flags { static final boolean DEBUG = true; }
class Test {
    public static void main(String[] args) {
        if (Flags.DEBUG)
            System.out.println("DEBUG is true");
    }
}

is compiled and executed, it produces the output:

DEBUG is true

Suppose that a new version of class Flags is produced:

class Flags { static final boolean DEBUG = false; }

If Flags is recompiled but not Test, then running the new binary with the existing binary of Test produces the output:

DEBUG is true

because DEBUG is a static constant variable, so its value could have been used in compiling Test without making a reference to the class Flags.

This behavior would also occur if Flags was an interface, as in the modified example:

interface Flags { boolean DEBUG = true; }
class Test {
    public static void main(String[] args) {
        if (Flags.DEBUG)
            System.out.println("DEBUG is true");
    }
}

In fact, because the fields of interfaces are always static and final, we recommend that only constant expressions be assigned to fields of interfaces. We note, but do not recommend, that if a field of primitive type of an interface may change, its value may be expressed idiomatically as in:

interface Flags {
    boolean debug = Boolean.valueOf(true).booleanValue();
}

ensuring that this value is not a constant expression. Similar idioms exist for the other primitive types.

Chapter 15: Expressions

15.1 Evaluation, Denotation, and Result

When an expression in a program is evaluated (executed), the result denotes one of three things:

If an expression denotes a variable, and a value is required for use in further evaluation, then the value of that variable is used. In this context, if the expression denotes a variable or a value, we may speak simply of the value of the expression.

Value set conversion (5.1.13) is applied to the result of every expression that produces a value, including when the value of a variable of type float or double is used.

An expression denotes nothing if and only if it is a method invocation (15.12) that invokes a method that does not return a value, that is, a method declared void (8.4). Such an expression can be used only as an expression statement (14.8) or as the single expression of a lambda body (15.27.2), because every other context in which an expression can appear requires the expression to denote something. An expression statement or lambda body that is a method invocation may also invoke a method that produces a result; in this case the value returned by the method is quietly discarded.

Evaluation of an expression can produce side effects, because expressions may contain embedded assignments, increment operators, decrement operators, and method invocations, as well as statements contained in switch expressions.

An expression occurs in either:

15.2 Forms of Expressions

Expressions can be broadly categorized into one of the following syntactic forms:

Precedence among operators is managed by a hierarchy of grammar productions. The lowest precedence operator is the arrow of a lambda expression (->), followed by the assignment operators. Thus, all expressions are syntactically included in the LambdaExpression and AssignmentExpression nonterminals:

Expression:
LambdaExpression
AssignmentExpression

When some expressions appear in certain contexts, they are considered poly expressions. The following forms of expressions may be poly expressions:

The rules determining whether an expression of one of these forms is a poly expression are given in the individual sections that specify these forms of expressions.

Expressions that are not poly expressions are standalone expressions. Standalone expressions are expressions of the forms above when determined not to be poly expressions, as well as all expressions of all other forms. Expressions of all other forms are said to have a standalone form.

Some expressions have a value that can be determined at compile time. These are constant expressions (15.28 15.29).

15.6 Normal and Abrupt Completion of Evaluation

Every expression has a normal mode of evaluation in which certain computational steps are carried out. The following sections describe the normal mode of evaluation for each kind of expression.

If all the steps are carried out without an exception being thrown, the expression is said to complete normally.

If, however, evaluation of an expression throws an exception, then the expression is said to complete abruptly. An abrupt completion always has an associated reason, which is always a throw with a given value.

Run-time exceptions are thrown by the predefined operators as follows:

A method invocation expression can also result in an exception being thrown if an exception occurs that causes execution of the method body to complete abruptly.

A class instance creation expression can also result in an exception being thrown if an exception occurs that causes execution of the constructor to complete abruptly.

Various linkage and virtual machine errors may also occur during the evaluation of an expression. By their nature, such errors are difficult to predict and difficult to handle.

If an exception occurs, then evaluation of one or more expressions may be terminated before all steps of their normal mode of evaluation are complete; such expressions are said to complete abruptly.

If evaluation of an expression requires evaluation of a subexpression, then abrupt completion of the subexpression always causes the immediate abrupt completion of the expression itself, with the same reason, and all succeeding steps in the normal mode of evaluation are not performed.

The terms "complete normally" and "complete abruptly" are also applied to the execution of statements (14.1). A statement may complete abruptly for a variety of reasons, not just because an exception is thrown.

15.12 Method Invocation Expressions

15.12.2 Compile-Time Step 2: Determine Method Signature

15.12.2.1 Identify Potentially Applicable Methods

The class or interface determined by compile-time step 1 (15.12.1) is searched for all member methods that are potentially applicable to this method invocation; members inherited from superclasses and superinterfaces are included in this search.

In addition, if the form of the method invocation expression is MethodName - that is, a single Identifier - then the search for potentially applicable methods also examines all member methods that are imported by single-static-import declarations and static-import-on-demand declarations of the compilation unit where the method invocation occurs (7.5.3, 7.5.4) and that are not shadowed at the point where the method invocation appears.

A member method is potentially applicable to a method invocation if and only if all of the following are true:

If the search does not yield at least one method that is potentially applicable, then a compile-time error occurs.

An expression is potentially compatible with a target type according to the following rules:

The definition of potential applicability goes beyond a basic arity check to also take into account the presence and "shape" of functional interface target types. In some cases involving type argument inference, a lambda expression appearing as a method invocation argument cannot be properly typed until after overload resolution. These rules allow the form of the lambda expression to still be taken into account, discarding obviously incorrect target types that might otherwise cause ambiguity errors.

15.12.2.5 Choosing the Most Specific Method

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error. In cases such as an explicitly typed lambda expression argument (15.27.1) or a variable arity invocation (15.12.2.4), some flexibility is allowed to adapt one signature to the other.

One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:

The above conditions are the only circumstances under which one method may be more specific than another.

A type S is more specific than a type T for any expression if S <: T (4.10).

A functional interface type S is more specific than a functional interface type T for an expression e if all of the following are true:

A method m1 is strictly more specific than another method m2 if and only if m1 is more specific than m2 and m2 is not more specific than m1.

A method is said to be maximally specific for a method invocation if it is accessible and applicable and there is no other method that is accessible and applicable that is strictly more specific.

If there is exactly one maximally specific method, then that method is in fact the most specific method; it is necessarily more specific than any other accessible method that is applicable. It is then subjected to some further compile-time checks as specified in 15.12.3.

It is possible that no method is the most specific, because there are two or more methods that are maximally specific. In this case:

15.15 Unary Operators Expressions

The operators +, -, ++, --, ~, !, and the cast operator (15.16) are called the unary operators, which are used to form unary expressions. In addition, the switch expression (15.28) is treated grammatically as a unary expression.

UnaryExpression:
PreIncrementExpression
PreDecrementExpression
+ UnaryExpression
- UnaryExpression
UnaryExpressionNotPlusMinus
PreIncrementExpression:
++ UnaryExpression
PreDecrementExpression:
-- UnaryExpression
UnaryExpressionNotPlusMinus:
PostfixExpression
~ UnaryExpression
! UnaryExpression
CastExpression
SwitchExpression

The following production from 15.16 is shown here for convenience:

CastExpression:
( PrimitiveType ) UnaryExpression
( ReferenceType {AdditionalBound} ) UnaryExpressionNotPlusMinus
( ReferenceType {AdditionalBound} ) LambdaExpression

Expressions with unary operators group right-to-left, so that -~x means the same as -(~x).

This portion of the grammar contains some tricks to avoid two potential syntactic ambiguities.

The first potential ambiguity would arise in expressions such as (p)+q, which looks, to a C or C++ programmer, as though it could be either a cast to type p of a unary + operating on q, or a binary addition of two quantities p and q. In C and C++, the parser handles this problem by performing a limited amount of semantic analysis as it parses, so that it knows whether p is the name of a type or the name of a variable.

Java takes a different approach. The result of the + operator must be numeric, and all type names involved in casts on numeric values are known keywords. Thus, if p is a keyword naming a primitive type, then (p)+q can make sense only as a cast of a unary expression. However, if p is not a keyword naming a primitive type, then (p)+q can make sense only as a binary arithmetic operation. Similar remarks apply to the - operator. The grammar shown above splits CastExpression into two cases to make this distinction. The nonterminal UnaryExpression includes all unary operators, but the nonterminal UnaryExpressionNotPlusMinus excludes uses of all unary operators that could also be binary operators, which in Java are + and -.

The second potential ambiguity is that the expression (p)++ could, to a C or C++ programmer, appear to be either a postfix increment of a parenthesized expression or the beginning of a cast, for example, in (p)++q. As before, parsers for C and C++ know whether p is the name of a type or the name of a variable. But a parser using only one-token lookahead and no semantic analysis during the parse would not be able to tell, when ++ is the lookahead token, whether (p) should be considered a Primary expression or left alone for later consideration as part of a CastExpression.

In Java, the result of the ++ operator must be numeric, and all type names involved in casts on numeric values are known keywords. Thus, if p is a keyword naming a primitive type, then (p)++ can make sense only as a cast of a prefix increment expression, and there had better be an operand such as q following the ++. However, if p is not a keyword naming a primitive type, then (p)++ can make sense only as a postfix increment of p. Similar remarks apply to the -- operator. The nonterminal UnaryExpressionNotPlusMinus therefore also excludes uses of the prefix operators ++ and --.

15.25 Conditional Operator ? :

The conditional operator ? : uses the boolean value of one expression to decide which of two other expressions should be evaluated.

ConditionalExpression:
ConditionalOrExpression
ConditionalOrExpression ? Expression : ConditionalExpression
ConditionalOrExpression ? Expression : LambdaExpression #

The conditional operator is syntactically right-associative (it groups right-to-left). Thus, a?b:c?d:e?f:g means the same as a?b:(c?d:(e?f:g)).

The conditional operator has three operand expressions. ? appears between the first and second expressions, and : appears between the second and third expressions.

The first expression must be of type boolean or Boolean, or a compile-time error occurs.

It is a compile-time error for either the second or the third operand expression to be an invocation of a void method.

In fact, by the grammar of expression statements (14.8), it is not permitted for a conditional expression to appear in any context where an invocation of a void method could appear.

There are three kinds of conditional expressions, classified according to the second and third operand expressions: boolean conditional expressions, numeric conditional expressions, and reference conditional expressions. The classification rules are as follows:

The process for determining the type of a conditional expression depends on the kind of conditional expression, as outlined in the following sections.

...

15.25.1 Boolean Conditional Expressions

Boolean conditional expressions are standalone expressions (15.2).

The type of a boolean conditional expression is determined as follows:

15.25.2 Numeric Conditional Expressions

Numeric conditional expressions are standalone expressions (15.2).

The type of a numeric conditional expression is determined as follows:

15.25.3 Reference Conditional Expressions

A reference conditional expression is a poly expression if it appears in an assignment context or an invocation context (5.2. 5.3). Otherwise, it is a standalone expression.

Where a poly reference conditional expression appears in a context of a particular kind with target type T, its second and third operand expressions similarly appear in a context of the same kind with target type T.

A poly reference conditional expression is compatible with a target type T if its second and third operand expressions are compatible with T.

The type of a poly reference conditional expression is the same as its target type.

The type of a standalone reference conditional expression is determined as follows:

Because reference conditional expressions can be poly expressions, they can "pass down" context to their operands. This allows lambda expressions and method reference expressions to appear as operands:

return ... ? (x `->` x) : (x `->` -x);

It also allows use of extra information to improve type checking of generic method invocations. Prior to Java SE 8, this assignment was well-typed:

List<String> ls = Arrays.asList();

but this was not:

List<String> ls = ... ? Arrays.asList() : Arrays.asList("a","b");

The rules above allow both assignments to be considered well-typed.

Note that a reference conditional expression does not have to contain a poly expression as an operand in order to be a poly expression. It is a poly expression simply by virtue of the context in which it appears. For example, in the following code, the conditional expression is a poly expression, and each operand is considered to be in an assignment context targeting Class<? super Integer>:

Class<? super Integer> choose(boolean b,
                              Class<Integer> c1,
                              Class<Number> c2) {
    return b ? c1 : c2;
}

If the conditional expression was not a poly expression, then a compile-time error would occur, as its type would be lub(Class<Integer>, Class<Number>) = Class<? extends Number> which is incompatible with the return type of choose.

15.28 switch Expressions

A switch expression is the expression analogue of the switch statement (14.11). It consists of a selector expression and a switch block (14.11.1). A switch expression matches the value of the selector expression against the switch labels associated with the switch block to determine which code contained in the switch block to execute to return a value. In contrast to a switch statement, the switch block is checked to ensure that the switch expression either completes normally with a value or completes abruptly.

SwitchExpression:
switch ( Expression ) SwitchBlock

The type of the selector expression must be char, byte, short, int, Character, Byte, Short, Integer, String, or an enum type (8.9), or a compile-time error occurs.

15.28.1 The switch block of a switch expression

Given a switch expression, all of the following must be true or a compile-time error occurs:

This forbids a switch expression to have an empty switch block.

Switch rules in switch expressions differ from those in switch statements (14.11.2). In switch expressions there is no restriction on a switch rule expression, whereas in switch statements it must be a statement expression (14.8).

The result expressions of a switch expression are determined as follows:

It is a compile-time error if a switch expression has no result expressions.

A switch expression is a poly expression if it appears in an assignment context or an invocation context (5.2, 5.3). Otherwise, it is a standalone expression.

Where a poly switch expression appears in a context of a particular kind with target type T, its result expressions similarly appear in a context of the same kind with target type T.

A poly switch expression is compatible with a target type T if each of its result expressions is compatible with T.

The type of a poly switch expression is the same as its target type.

The type of a standalone switch expression is determined as follows:

15.28.2 Run-Time Evaluation of switch Expressions

When the switch expression is executed, first the selector expression is evaluated; exactly one of three outcomes are possible.

  1. If evaluation of the selector expression completes abruptly for some reason, the switch expression completes abruptly for the same reason.

  2. If the selector expression evaluates to null, then a NullPointerException is thrown and the entire switch expression completes abruptly for that reason.

  3. Otherwise, execution continues by determining if a switch label associated with the switch block matches (14.11.1) the value of the selector expression.

    If no switch label matches, then an IncompatibleClassChangeError is thrown and the entire switch expression completes abruptly for that reason.

    If a switch label matches, then one of the following applies:

    • If it is the switch label for a switch rule expression, then this expression is evaluated. If the result of evaluation is a value, then the switch expression completes normally with the same value.

    • If it is the switch label for a switch rule block, then the block is executed.

    • If it is the switch label for a switch rule throw statement, then the throw statement is executed.

    • Otherwise, all the statements in the switch block after the matching switch label are executed in order.

    If execution of any statement or expression completes abruptly, it is handled as follows:

    • If execution of an expression completes abruptly for a reason, then the switch expression completes abruptly for the same reason.

    • If execution of a statement completes abruptly for the reason of a yield with value V, then the switch expression completes normally and the value of the switch expression is V.

    • If execution of a statement completes abruptly for any reason other than a yield with value, then the switch expression completes abruptly for the same reason.

15.28 15.29 Constant Expressions

ConstantExpression:
Expression

A constant expression is an expression denoting a value of primitive type or a String that does not complete abruptly and is composed using only the following:

Constant expressions of type String are always "interned" so as to share unique instances, using the method String.intern.

A constant expression is always treated as FP-strict (15.4), even if it occurs in a context where a non-constant expression would not be considered to be FP-strict.

Constant expressions are used as case labels in switch statements and switch expressions (14.11, 15.28) and have a special significance in assignment contexts (5.2) and the initialization of a class or interface (12.4.2). They may also govern the ability of a while, do, or for statement to complete normally (14.21), and the type of a conditional operator ? : with numeric operands.

Example 15.28-1 15.29-1. Constant Expressions

true
(short)(1*2*3*4*5*6)
Integer.MAX_VALUE / 2
2.0 * Math.PI
"The integer " + Long.MAX_VALUE + " is mighty big."

Chapter 16: Definite Assignment

16.1 Definite Assignment and Expressions

16.1.7 switch Expressions

Suppose that the switch expression has result expressions e1, ..., en, all of which are boolean-valued.

The following rules apply only if the switch block of a switch expression (15.28) consists of switch labeled statement groups:

The following rules apply only if the switch block of a switch expression consists of switch rules:

16.1.8 switch Expressions

Suppose that the switch expression has result expressions e1, ..., en, not all of which are boolean-valued.

16.1.7 16.1.9 Other Expressions of Type boolean

Suppose that e is an expression of type boolean and is not a boolean constant expression, logical complement expression !a, conditional-and expression a && b, conditional-or expression a || b, or conditional expression a ? b : c.

16.1.8 16.1.10 Assignment Expressions

Consider an assignment expression a = b, a += b, a -= b, a *= b, a /= b, a %= b, a <<= b, a >>= b, a >>>= b, a &= b, a |= b, or a ^= b (15.26).

Note that if a is V and V is not definitely assigned before a compound assignment such as a &= b, then a compile-time error will necessarily occur. The first rule for definite assignment stated above includes the disjunct "a is V" even for compound assignment expressions, not just simple assignments, so that V will be considered to have been definitely assigned at later points in the code. Including the disjunct "a is V" does not affect the binary decision as to whether a program is acceptable or will result in a compile-time error, but it affects how many different points in the code may be regarded as erroneous, and so in practice it can improve the quality of error reporting. A similar remark applies to the inclusion of the conjunct "a is not V" in the first rule for definite unassignment stated above.

16.1.9 16.1.11 Operators ++ and --

16.1.10 16.1.12 Other Expressions

If an expression is not a boolean constant expression, and is not a preincrement expression ++a, predecrement expression --a, postincrement expression a++, postdecrement expression a--, logical complement expression !a, conditional-and expression a && b, conditional-or expression a || b, conditional expression a ? b : c, assignment expression, or lambda expression, then the following rules apply:

There is a piece of subtle reasoning behind the assertion that a variable V can be known to be definitely unassigned after a method invocation expression. Taken by itself, at face value and without qualification, such an assertion is not always true, because an invoked method can perform assignments. But it must be remembered that, for the purposes of the Java programming language, the concept of definite unassignment is applied only to blank final variables. If V is a blank final local variable, then only the method to which its declaration belongs can perform assignments to V. If V is a blank final field, then only a constructor or an initializer for the class containing the declaration for V can perform assignments to V; no method can perform assignments to V. Finally, explicit constructor invocations (8.8.7.1) are handled specially (16.9); although they are syntactically similar to expression statements containing method invocations, they are not expression statements and therefore the rules of this section do not apply to explicit constructor invocations.

If an expression is a lambda expression, then the following rules apply:

For any immediate subexpression y of an expression x, where x is not a lambda expression, V is [un]assigned before y iff one of the following is true:

16.2 Definite Assignment and Statements

16.2.9 switch Statements

If a switch block contains at least one block-statement-group, then the following rules also apply:

16.2.13 break, yield, continue, return, and throw Statements

Chapter 18: Type Inference

18.2 Reduction

18.2.1 Expression Compatibility Constraints

A constraint formula of the form ‹Expression T› is reduced as follows:

By treating nested generic method invocations as poly expressions, we improve the behavior of inference for nested invocations. For example, the following is illegal in Java SE 7 but legal in Java SE 8:

ProcessBuilder b = new ProcessBuilder(Collections.emptyList());
  // ProcessBuilder's constructor expects a List<String>

When both the outer and the nested invocation require inference, the problem is more difficult. For example:

List<String> ls = new ArrayList<>(Collections.emptyList());

Our approach is to "lift" the bounds inferred for the nested invocation (simply { α <: Object } in the case of emptyList) into the outer inference process (in this case, trying to infer β where the constructor is for type ArrayList<β>). We also infer dependencies between the nested inference variables and the outer inference variables (the constraint ‹List<α> Collection<β>› would reduce to the dependency α = β). In this way, resolution of the inference variables in the nested invocation can wait until additional information can be inferred from the outer invocation (based on the assignment target, β = String).

...

18.5 Uses of Inference

18.5.2 Invocation Type Inference

18.5.2.2 Additional Argument Constraints

The invocation type for the chosen method is determined after considering additional constraints that may be implied by the argument expressions of the method invocation expression, as follows:

The process of reducing additional argument constraints may require carefully ordering constraint formulas of the forms ‹Expression T›, ‹LambdaExpression throws T›, and ‹MethodReference throws T›. To facilitate this ordering, the input variables of these constraints are defined as follows:

The output variables of these constraints are all inference variables mentioned by the type on the right-hand side of the constraint, T, that are not input variables.

18.5.4 More Specific Method Inference

When testing that one applicable method is more specific than another (15.12.2.5), where the second method is generic, it is necessary to test whether some instantiation of the second method's type parameters can be inferred to make the first method more specific than the second.

Let m1 be the first method and m2 be the second method. Where m2 has type parameters P1, ..., Pp, let α1, ..., αp be inference variables, and let θ be the substitution [P1:=α1, ..., Pp:=αp].

Let e1, ..., ek be the argument expressions of the corresponding invocation. Then:

Note that no substitution is applied to S1, ..., Sk; even if m1 is generic, the type parameters of m1 are treated as type variables, not inference variables.

The process to determine if m1 is more specific than m2 is as follows: