Specification for JEP 325: Switch Expressions

June 2018, Edited: Jan 2019

This document proposes changes to the Java Language Specification to support extensions to the switch statement so that it can be used as either a statement or an expression, along with associated changes to the break statement. See JEP 325 for an overview.

To assist reading, this document contains complete replacements for sections 5.6, 11.2.1, 11.2.2, 14.1, 14.11, 14.15, 14.16, 14.17, 14.21, 15.1, 15.2, 15.6, 15.12.2.1, 15.12.2.5, 15.15, 15.25, 16.2.9, 16.2.13, 18.5.2, and 18.5.4 along with entirely new sections 5.6.3, 16.1.7, and 16.1.8. It is proposed to introduce a new section on switch expressions as 15.28 and renumber (and update) the current section on constant expressions as 15.29. In the replacement sections, new text is highlighted like this and deleted text is highlighted like this.

5.6. Numeric Contexts

Numeric contexts apply to the operands of an arithmetic operator, or the result expressions of a switch expression.

Numeric contexts allow the use of:

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

The two three kinds of numeric promotion are unary numeric promotion (5.6.1), and binary numeric promotion (5.6.2), and switch numeric promotion (5.6.3).

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:

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.

5.6.3 Switch Numeric Promotion

When a switch expression applies numeric promotion to a set of result expressions, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:

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

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.

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 (Exceptions)], 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.11. The switch Statement

[Editorial note: The existing section 14.11 is replaced in its entirety with the following.]

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

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.

14.11.1 Switch Block

SwitchBlock:

{ SwitchLabeledRule { SwitchLabeledRule } }
{ { SwitchLabeledStatementGroup } { SwitchLabel : } }

SwitchLabeledRule:

SwitchLabeledExpression
SwitchLabeledBlock
SwitchLabeledThrowStatement

SwitchLabeledExpression:

SwitchLabel -> Expression ;

SwitchLabeledBlock:

SwitchLabel -> Block

SwitchLabeledThrowStatement:

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 labeled rules, which use ‘->’ to introduce either a switch labeled expression, block, or throw statement; or

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

A switch label can be either a case label or a default label. Every case label has at least one case constant. case and default labels, and the case constants of case labels, are said to be associated with the switch block.

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

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

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 not the type String, then it is first subject to an unboxing conversion (5.1.8)):

The prohibition against using null as a case constant prevents code being written that can never be executed. If the 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 either the switch block not matching or even the default label matching.

In C and C++ the body of a switch statement can be a statement and statements with case 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.

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!");
}

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 labeled rules in switch statements differ from those in switch expressions (15.28). In switch statements they must be switch labeled statement expressions, whereas in switch expressions they may be any switch labeled expression (15.28.1).

A switch statement is permitted to have an empty switch block.

In this respect, switch statements differ from switch expressions (15.28) which are not permitted to have an empty switch block.

14.11.3 Execution of a switch Statement

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 execution of any statement or expression completes abruptly, it is handled as follows:

The case of abrupt completion because of a value break statement is prohibited by (14.15).

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

When a selector expression matches a switch label within a switch labeled rule, only the labeled expression or statement is executed and nothing else. In the other case, 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, switch labeled rules can be used, or empty break statements can be used to explicitly indicate where control should be transfered, as follows:

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

14.15. The break Statement

A break statement transfers control out of an enclosing statement, or causes an enclosing switch expression to produce a specified value.

BreakStatement:

break [Identifier] ;
break Identifier ;
break Expression ;
break ;

There are three kinds of break statement:

  1. A break statement with label Identifier; or, more succinctly, a labeled break statement.
  2. A break statement with value Expression; or, more succinctly, a value break statement.
  3. A break statement with no label or value; or, more succinctly, an empty break statement.

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.

An empty break statement attempts to transfer control to the innermost enclosing switch, while, do, or for statement; this statement, which is called the break target, then immediately completes normally.

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

A labeled 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.

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

It is a compile-time error if the break target of a value break statement contains any switch, while, do or for statement that encloses the value break statement.

It is a compile-time error if a value break statement is contained in a labeled statement, where Expression is a simple name (15.14.1) that is the same identifier as the label.

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

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

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

Execution of a labeled 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; or, more succinctly, a labeled continue statement.
  2. A continue statement with no label; or, more succinctly, an empty continue statement.

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 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.

An empty continue statement attempts to transfer control to the innermost enclosing while, do, or for statement; this statement, which is called the continue target, then immediately ends the current iteration and begins a new one.

A labeled 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.

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

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

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

Execution of a labeled 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 Expression ;
return ;

There are two kinds of return statement:

  1. A return statement with value Expression; or, more succinctly, a value return statement.
  2. A return statement with no value; or, more succinctly, an empty return statement.

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, intializer, or lambda expression; this declaration is called the return target. In the case of a value return statement, 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, or if the return target contains any enclosing method, constructor, initializer, lambda expression, or switch expression that contains the return statement.

It is a compile-time error if the return target for 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:

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.

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. 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.

It is a compile-time error if the return target of an empty return statement is a method, and that method is not declared ‘void’.

It is a compile-time error if the return target of a value return statement is a constructor or a method that is declared ‘void’; or if the return target is a method with declared return type T, and Expression is not assignable (5.2) to T.

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

Execution of a value return statement 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. Unreachable Statements

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.

This ability to “conditionally compile” has no relationship to binary compatibility ([13 (Binary Compatibility)]). If a set of classes that use such a “flag” variable 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, but no LinkageError will occur. A change to the value of a flag is, therefore, binary compatible with pre-existing binaries, but not behaviorally compatible.

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 or constructor 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.2815.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.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:

Whether a member method is accessible at a method invocation depends on the access modifier (public, protected, no modifier (package access), or private) in the member’s declaration and on where the method invocation appears.

This clause implies that a non-generic method may be potentially applicable to an invocation that supplies explicit type arguments. Indeed, it may turn out to be applicable. In such a case, the type arguments will simply be ignored.

This rule stems from issues of compatibility and principles of substitutability. Since interfaces or superclasses may be generified independently of their subtypes, we may override a generic method with a non-generic one. However, the overriding (non-generic) method must be applicable to calls to the generic method, including calls that explicitly pass type arguments. Otherwise the subtype would not be substitutable for its generified supertype.

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 T is not a subtype of S and one of the following is true (where U1 … Uk and R1 are the parameter types and return type of the function type of the capture of S, and V1 … Vk and R2 are the parameter types and return type of the function type of T):

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 applicable and accessible 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.

The following tables summarize the rules above by giving the type of a conditional expression for all possible types of its second and third operands. bnp(..) means to apply binary numeric promotion. The form “T | bnp(..)” is used where one operand is a constant expression of type int and may be representable in type T, where binary numeric promotion is used if the operand is not representable in type T. The operand type Object means any reference type other than the null type and the eight wrapper classes Boolean, Byte, Short, Character, Integer, Long, Float, Double.

[Editorial note: Tables 15.25.A-E will appear here unchanged in the final spec. Not reproduced in this document for brevity.]

At run time, the first operand expression of the conditional expression is evaluated first. If necessary, unboxing conversion is performed on the result.

The resulting boolean value is then used to choose either the second or the third operand expression:

The chosen operand expression is then evaluated and the resulting value is converted to the type of the conditional expression as determined by the rules stated below.

This conversion may include boxing or unboxing conversion (5.1.7, 5.1.8).

The operand expression not chosen is not evaluated for that particular evaluation of the conditional expression.

15.28. switch Expressions

[Editorial note: This is a new section. The existing section 15.28 on constant expressions is renumbered as 15.29.]

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:

Switch labeled rules in switch expressions differ from those in switch statements (14.11.2). In switch expressions they may be any switch labeled expression, whereas in switch statements they must be switch labeled statement expressions (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 Execution of a switch Expression

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 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 execution of any statement or expression completes abruptly, it is handled as follows:

15.2815.29. Constant Expressions

[Editorial note: This section is the current 15.28 renumbered as 15.29.]

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 for assignment conversion (5.2) and 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-115.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."

[Editorial note: Existing section 16.1.7 renumbered to 16.1.9, 16.1.8 renumbered to 16.1.10, 16.1.9 renumbered to 16.1.11, and 16.1.10 renumbered to 16.1.12.]

16.1.7. switch Expression

[Editorial note: This is a new subsection.]

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 labeled rules:

16.1.8 switch Expression

[Editorial note: This is a new subsection.]

Suppose that the switch expression has result expressions e1, …, en, not 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 labeled rules:

16.2.9. switch Statements

The following rules apply only if the switch block of a switch statement (14.11) is empty, consists of just switch labels, or consists of switch labeled statement groups:

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

The following rules apply only if the switch block of the switch statement consists of switch labeled rules:

16.2.13. break, continue, return, and throw Statements

The notion that a variable is “[un]assigned after” a statement or expression really means “is [un]assigned after the statement or expression completes normally”. Because a break, continue, return, or throw statement never completes normally, it vacuously satisfies this notion.

18.2.1. Expression Compatibility Constraints

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

A constraint formula of the form ‹LambdaExpression → T›, where T mentions at least one inference variable, is reduced as follows:

The key piece of information to derive from a compatibility constraint involving a lambda expression is the set of bounds on inference variables appearing in the target function type’s return type. This is crucial, because functional interfaces are often generic, and many methods operating on these types are generic, too.

In the simplest case, a lambda expression may simply provide a lower bound for an inference variable:

<T> List<T> makeThree(Factory<T> factory) { ... }
String s = makeThree(() -> "abc").get(2);

In more complex cases, a result expression may be a poly expression - perhaps even another lambda expression - and so the inference variable might be passed through multiple constraint formulas with different target types before a bound is produced.

Most of the work described in this section precedes assertions about the result expressions; its purpose is to derive the lambda expression’s function type, and to check for expressions that are clearly disqualified from compatibility.

We do not attempt to produce bounds on inference variables that appear in the target function type’s throws clause. This is because exception containment is not part of compatibility (15.27.3) - in particular, it must not influence method applicability (18.5.1). However, we do get bounds on these variables later, because invocation type inference (18.5.2) produces exception containment constraint formulas ([18.2.5]).

Note that if the target type is an inference variable, or if the target type’s parameter types contain inference variables, we produce false. During invocation type inference (18.5.2), extra substitutions are performed in order to instantiate these inference variables, thus avoiding this scenario. (In other words, reduction will, in practice, never be “invoked” with a target type of one of these forms.)

Finally, note that the result expressions of a lambda expression are required by 15.27.3 to be compatible in an assignment context with the target type’s return type, R. If R is a proper type, such as Byte derived from Function<α,Byte``>, then assignability is easy enough to test, and reduction does so above. If R is not a proper type, such as α derived from Function<String,α>, then we make the simplifying assumption above that loose invocation compatibility will be sufficient. The difference between assignment compatibility and loose invocation compatibility is that only assignment allows narrowing of constant expressions, such as Byte b = 100;. Consequently, our simplifying assumption is not completeness-preserving: given target return type α and an integer literal result expression 100, it is conceivable that α could be instantiated to Byte, but reduction will not in fact produce such a bound.

A constraint formula of the form ‹MethodReference → T›, where T mentions at least one inference variable, is reduced as follows:

18.5.2. Invocation Type Inference

Given a method invocation that provides no explicit type arguments, and a corresponding most specific applicable generic method m, the process to infer the invocation type (15.12.2.6) of the chosen method is as follows:

Invocation type inference may require carefully sequencing the reduction of constraint formulas of the forms ‹Expression → T›, ‹LambdaExpressionthrows T›, and ‹MethodReferencethrows T›. To facilitate this sequencing, 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.

It is important to note that two “rounds” of inference are involved in finding the type of a method invocation. This is necessary to allow a target type to influence the type of the invocation without allowing it to influence the choice of an applicable method. The first round produces a bound set and tests that a resolution exists, but does not commit to that resolution. The second round reduces additional constraints and then performs a second resolution, this time “for real”.

Consider the example from the previous section:

List<Number> ln = Arrays.asList(1, 2.0);

The most specific applicable method was identified as:

public static <T> List<T> asList(T... a)

In order to complete type-checking of the method invocation, we must determine whether it is compatible with its target type, List<Number>.

The bound set used to demonstrate applicability in the previous section, B2, was:

{ α <: Object, Integer <: α, Double <: α }

The new constraint formula set is as follows:

{ ‹List<α>List<Number>› }

This compatibility constraint produces an equality bound for α, which is included in the new bound set, B3:

{ α <: Object, Integer <: α, Double <: α, α = Number }

These bounds are trivially resolved:

α = Number

Finally, we perform a substitution on the declared return type of asList to determine that the method invocation has type List<Number>; clearly, this is compatible with the target type.

This inference strategy is different than the Java SE 7 Edition of The Java® Language Specification, which would have instantiated α based on its lower bounds (before even considering the invocation’s target type), as we did in the previous section. This would result in a type error, since the resulting type is not a subtype of List<Number>.

Under various special circumstances, based on the bounds appearing in B2, we eagerly resolve an inference variable that appears as the return type of the invocation. This is to avoid unfortunate situations in which the usual constraint, ‹R θ → T›, is not completeness-preserving. It is, unfortunately, possible that by eagerly resolving the variable, we are unable to make use of bounds that would be inferred later. It is also possible that, in some cases, bounds that will later be inferred from the invocation arguments (such as implicitly typed lambda expressions) would have caused a different outcome if they had been present in B2. Despite these limitations, the strategy allows for reasonable outcomes in typical use cases, and is backwards compatible with the algorithm in the Java SE 7 Edition of The Java® Language Specification.

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: