Preview Language Specification for JEP 325: Switch Expressions

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

See JEP 325 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 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 arithmetic operators, and some other expressions that operate on numbers.

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 one or more expressions in a numeric context 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 , in the case of an arithmetic operation, the operation is defined for values of type T.

Numeric promotion applies the following rules:

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

The following subsections are small enough that they can be removed, with their contents collapsed into this section.

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.

A unary numeric promotion applies numeric promotion to an operand expression and a notional non-constant expression of type int.

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

Example 5.6.1-1 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

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.

A binary numeric promotion applies numeric promotion to two operand expressions and a notional non-constant expression of type int.

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

Numeric promotion is performed directly on expressions in the following situations:

Example 5.6.2-1 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.

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 (Chapter 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

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

Any statement immediately contained by the switch block may be labeled with one or more switch labels, which are case or default labels. Every case label has a case constant, 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.

Every case constant associated with a given switch block must be either a constant expression (15.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.

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

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

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

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 labels a 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 labels a statement, then the statement is executed. If this statement completes normally, then the switch statement completes normally.

    • If it labels 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:

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 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, then break statements should be used, as in this example 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 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, or causes an enclosing switch expression to produce a specified value.

BreakStatement:
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 An empty break statement 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 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.

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

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 An empty continue statement 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 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.

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 labeled continue statement 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, 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 ;

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 a return statement is contained in the return target for a return statement is 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 an empty return statement 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 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.

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

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:

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 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 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 labels an 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 labels a statement, then the 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 break 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 break 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 labeled rules:

16.1.8 switch Expressions

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

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

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: