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

Record Classes

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

This document describes changes to the Java Language Specification, as modified by Consistent Class and Interface Terminology and Local and Nested Static Declarations, to support Record Classes, a feature of Java SE 16. See the draft JEP for an overview of the feature.

The changes are the same as those in the second preview of Records in Java SE 15, except for minor editorial changes and the following:

A companion document describes the changes needed to the Java Virtual Machine Specification to support record classes.

A further companion document describes changes to the Java Object Serialization Specification to support serializable record classes.

Changes are described with respect to existing sections of the JLS. 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 1: Introduction

1.1 Organization of the Specification

...

Chapter 8 describes classes. The members of classes are classes, interfaces, fields (variables) and methods. Class variables exist once per class. Class methods operate without reference to a specific object. Instance variables are dynamically created in objects that are instances of classes. Instance methods are invoked on instances of classes; such instances become the current object this during their execution, supporting the object-oriented programming style.

Classes support single inheritance, in which each class has a single superclass. Each class inherits members from its superclass, and ultimately from the class Object. Variables of a class type can reference an instance of the named class or of any subclass of that class, allowing new classes to be used with existing methods, polymorphically.

Classes support concurrent programming with synchronized methods. Methods declare the checked exceptions that can arise from their execution, which allows compile-time checking to ensure that exceptional conditions are handled. Objects can declare a finalize method that will be invoked before the objects are discarded by the garbage collector, allowing the objects to clean up their state.

For simplicity, the language has neither declaration "headers" separate from the implementation of a class nor separate type and class hierarchies.

A special form of classes, enum classes, support the definition of small sets of values and their manipulation in a type safe manner. Enum classes are a special kind of class that support the definition of small sets of values which can then be used in a type safe manner. Unlike enumerations in other languages, enum constants are objects and may have their own methods.

Record classes are another special kind of class that support the compact expression of simple objects that serve as aggregates of values.

...

1.5 Preview Features

The following text will be added to the description of the preview feature: Record Types.

The following are essential API elements associated with Record Types:

Chapter 3: Lexical Structure

3.8 Identifiers

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

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

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

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

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

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

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

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

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

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

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

Examples of identifiers are:

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

A type identifier is an identifier that is not the character sequence var or the character sequence yield any identifier other than the character sequences var, yield, and record.

TypeIdentifier:
Identifier but not var, or yield or record

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

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

UnqualifiedMethodIdentifier:
Identifier but not yield

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

3.9 Keywords

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

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

The keywords const and goto are reserved, even though they are not currently used. This may allow a Java compiler to produce better error messages if these C++ keywords incorrectly appear in programs. The keyword _ (underscore) is reserved for possible future use in parameter declarations.

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

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

Chapter 4: Types, Values, and Variables

4.11 Where Types Are Used

Types are used in most kinds of declaration and in certain kinds of expression. Specifically, there are 16 17 type contexts where types are used:

Also, types are used as:

Finally, there are two special terms in the Java programming language which denote the use of a type:

The meaning of types in type contexts is given by:

Some type contexts restrict how a reference type may be parameterized:

In any type context where a type is used, it is possible to annotate the keyword denoting a primitive type or the Identifier denoting the simple name of a reference type. It is also possible to annotate an array type by writing an annotation to the left of the [ at the desired level of nesting in the array type. Annotations in these locations are called type annotations, and are specified in 9.7.4. Here are some examples:

Five Six of the type contexts which appear in declarations occupy the same syntactic real estate as a number of declaration contexts (9.6.4.1):

The fact that the same syntactic location in a program can be both a type context and a declaration context arises because the modifiers for a declaration immediately precede the type of the declared entity. 9.7.4 explains how an annotation in such a location is deemed to appear in a type context or a declaration context or both.

Example 4.11-1. Usage of a Type

import java.util.Random;
import java.util.Collection;
import java.util.ArrayList;

class MiscMath<T extends Number> {
    int divisor;
    MiscMath(int divisor) { this.divisor = divisor; }
    float ratio(long l) {
        try {
            l /= divisor;
        } catch (Exception e) {
            if (e instanceof ArithmeticException)
                l = Long.MAX_VALUE;
            else
                l = 0;
        }
        return (float)l;
    }
    double gausser() {
        Random r = new Random();
        double[] val = new double[2];
        val[0] = r.nextGaussian();
        val[1] = r.nextGaussian();
        return (val[0] + val[1]) / 2;
    }
    Collection<Number> fromArray(Number[] na) {
        Collection<Number> cn = new ArrayList<Number>();
        for (Number n : na) cn.add(n);
        return cn;
    }
    <S> void loop(S s) { this.<S>loop(s); }  
}

In this example, types are used in declarations of the following:

and in expressions of the following kinds:

Chapter 6: Names

6.1 Declarations

A declaration introduces an entity into a program and includes an identifier (3.8) that can be used in a name to refer to this entity. The identifier is constrained to be a type identifier when the entity being introduced is a class, interface, or type parameter.

A declared entity is one of the following:

Constructors (8.8) are also introduced by declarations (including implicit declarations in record class declarations (8.10.4)), but use the name of the class in which they are declared rather than introducing a new name.

...

6.5 Determining the Meaning of a Name

6.5.1 Syntactic Classification of a Name According to Context

A name is syntactically classified as a ModuleName in these contexts:

A name is syntactically classified as a PackageName in these contexts:

A name is syntactically classified as a TypeName in these contexts:

The extraction of a TypeName from the identifiers of a ReferenceType in the 16 contexts above is intended to apply recursively to all sub-terms of the ReferenceType, such as its element type and any type arguments.

For example, suppose a field declaration uses the type p.q.Foo[]. The brackets of the array type are ignored, and the term p.q.Foo is extracted as a dotted sequence of Identifiers to the left of the brackets in an array type, and classified as a TypeName. A later step determines which of p, q, and Foo is a type name or a package name.

As another example, suppose a cast operator uses the type p.q.Foo<? extends String>. The term p.q.Foo is again extracted as a dotted sequence of Identifier terms, this time to the left of the < in a parameterized type, and classified as a TypeName. The term String is extracted as an Identifier in an extends clause of a wildcard type argument of a parameterized type, and classified as a TypeName.

A name is syntactically classified as an ExpressionName in these contexts:

A name is syntactically classified as a MethodName in this context:

A name is syntactically classified as a PackageOrTypeName in these contexts:

A name is syntactically classified as an AmbiguousName in these contexts:

The effect of syntactic classification is to restrict certain kinds of entities to certain parts of expressions:

Chapter 8: Classes

Class declarations define new classes and describe how they are implemented (8.1).

A top level class (7.6) is a class that is declared at the top level of a compilation unit.

A nested class is any class whose declaration occurs within the body of another class or interface. A nested class may be a member class (8.5, 9.5), a local class (14.3), or an anonymous class (15.9.5).

An inner class (8.1.3) is a nested class that can refer to enclosing class instances, local variables, and type variables.

An enum class (8.9) is a class declared with special syntax that defines a small set of named class instances.

A record class (8.10) is a class declared with special syntax that defines a simple aggregate of values.

This chapter discusses the common semantics of all classes. Details that are specific to particular kinds of classes are discussed in the sections dedicated to these constructs.

A class may be declared abstract (8.1.1.1) and must be declared abstract if it is incompletely implemented; such a class cannot be instantiated, but can be extended by subclasses. A class may be declared final (8.1.1.2), in which case it cannot have subclasses. A class can use access control (6.6) to prevent references to the class from other classes, interfaces, packages, or modules. Each class except Object is an extension of (that is, a subclass of) a single existing class (8.1.4) and may implement interfaces (8.1.5). Classes may be generic (8.1.2), that is, they may declare type variables whose bindings may differ among different instances of the class.

Classes may be decorated with annotations (9.7) just like any other kind of declaration.

The body of a class declares members (fields, methods, classes, and interfaces), instance and static initializers, and constructors (8.1.6). The scope (6.3) of a member (8.2) is the entire body of the declaration of the class to which the member belongs. Field, method, member class, member interface, and constructor declarations may include the access modifiers (6.6) public, protected, or private. The members of a class include both declared and inherited members (8.2). Newly declared fields can hide fields declared in a superclass or superinterface. Newly declared member classes and interfaces can hide member classes and interfaces declared in a superclass or superinterface. Newly declared methods can hide, implement, or override methods declared in a superclass or superinterface.

Field declarations (8.3) describe class variables, which are incarnated once, and instance variables, which are freshly incarnated for each instance of the class. A field may be declared final (8.3.1.2), in which case it can be assigned to only once. Any field declaration may include an initializer.

Member class declarations (8.5) describe nested classes that are members of the surrounding class. Member classes may be static or they may be inner classes.

Member interface declarations (8.5) describe nested interfaces that are members of the surrounding class.

Method declarations (8.4) describe code that may be invoked by method invocation expressions (15.12). A class method is invoked relative to the class; an instance method is invoked with respect to some particular object that is an instance of a class. A method whose declaration does not indicate how it is implemented must be declared abstract. A method may be declared final (8.4.3.3), in which case it cannot be hidden or overridden. A method may be implemented by platform-dependent native code (8.4.3.4). A synchronized method (8.4.3.6) automatically locks an object before executing its body and automatically unlocks the object on return, as if by use of a synchronized statement (14.19), thus allowing its activities to be synchronized with those of other threads (17).

Method names may be overloaded (8.4.9).

Instance initializers (8.6) are blocks of executable code that may be used to help initialize an instance when it is created (15.9).

Static initializers (8.7) are blocks of executable code that may be used to help initialize a class.

Constructors (8.8) are similar to methods, but cannot be invoked directly by a method call; they are used to initialize new class instances. Like methods, they may be overloaded (8.8.8).

8.1 Class Declarations

A class declaration specifies a new class.

There are two three kinds of class declarations: normal class declarations, and enum declarations, and record declarations.

ClassDeclaration:
NormalClassDeclaration
EnumDeclaration
RecordDeclaration
NormalClassDeclaration:
{ClassModifier} class TypeIdentifier [TypeParameters]
[ClassExtends] [ClassImplements] ClassBody

A class is also implicitly declared by a ClassInstanceCreationExpression (15.9.5) or EnumConstant (8.9.1) that ends with a class body.

The TypeIdentifier in a class declaration specifies the name of the class.

It is a compile-time error if a class has the same simple name as any of its enclosing classes or interfaces.

The scope and shadowing of a class declaration is specified in 6.3 and 6.4.

8.1.1 Class Modifiers

8.1.1.4 static Classes

The static keyword indicates that a nested class is not an inner class (8.1.3). The class has no immediately enclosing instance and cannot directly reference enclosing type variables (6.5.5.1); enclosing instance variables, local variables, formal parameters, or exception parameters (6.5.6.1); or enclosing instance methods (15.12.3).

A local class declaration may not use the static keyword (14.3).

Nested enum and record classes are implicitly declared static. A member enum or record class may redundantly specify the static modifier; a local enum or record class may not (8.9).

8.1.3 Inner Classes and Enclosing Instances

An inner class is a nested class that is not explicitly or implicitly declared static.

An inner class may be a non-static member class (8.5), a non-static local class (14.3), or an anonymous class (15.9.5). Nested interfaces (9.1), nested enum classes (8.9), nested record classes (8.10), member annotation interfaces (9.6), and member classes of interfaces (9.5) are implicitly static, so are never considered to be inner classes.

...

8.1.4 Superclasses

The optional extends clause in a normal class declaration specifies the direct superclass type of the current class.

ClassExtends:
extends ClassType

The extends clause must not appear in the definition of the class Object, or a compile-time error occurs, because it is the primordial class and has no direct superclass type.

The ClassType must name an accessible class type (6.6), or a compile-time error occurs.

It is a compile-time error if the ClassType names a class that is final, because final classes are not allowed to have subclasses (8.1.1.2).

It is a compile-time error if the ClassType names the class Enum, which can only be extended by an enum class (8.9), or the class Record, which can only be extended by a record class (8.10).

If the ClassType has type arguments, it must denote a well-formed parameterized type (4.5), and none of the type arguments may be wildcard type arguments, or a compile-time error occurs.

The direct superclass type of a class whose declaration lacks an extends clause is as follows:

The direct superclass of a class is the class named by its direct superclass type. The direct superclass is the class from whose implementation the implementation of the current class is derived.

The superclass relationship is the transitive closure of the direct superclass relationship. A class A is a superclass of class C if either of the following is true:

A class is said to be a direct subclass of its direct superclass, and a subclass of each of its superclasses.

...

8.5 Member Class and Interface Declarations

A member class is a class whose declaration is directly enclosed in the body of another class or interface declaration (8.1.6, 9.1.4). A member class may be an enum class (8.9) or a record class (8.10).

A member interface is an interface whose declaration is directly enclosed in the body of another class or interface declaration (8.1.6, 9.1.4). A member interface may be an annotation interface (9.6).

The accessibility of a member class or interface declaration in a class is specified by its access modifier, or by 6.6 if lacking an access modifier.

The scope and shadowing of a member class or interface is specified in 6.3 and 6.4.

If a class declares a member class or interface with a certain name, then the declaration of that class or interface is said to hide any and all accessible declarations of member classes and interfaces with the same name in superclasses and superinterfaces of the class.

In this respect, hiding of member classes and interfaces is similar to hiding of fields (8.3).

A class inherits from its direct superclass and direct superinterfaces all the non-private member classes and interfaces of the superclass and superinterfaces that are both accessible to code in the class and not hidden by a declaration in the class.

It is possible for a class to inherit more than one member class or interface with the same name, either from its superclass and superinterfaces or from its superinterfaces alone. Such a situation does not in itself cause a compile-time error. However, any attempt within the body of the class to refer to any such member class or interface by its simple name will result in a compile-time error, because the reference is ambiguous.

There might be several paths by which the same member class or interface declaration is inherited from an interface. In such a situation, the member class or interface is considered to be inherited only once, and it may be referred to by its simple name without ambiguity.

8.8 Constructor Declarations

A constructor is used in the creation of an object that is an instance of a class (12.5, 15.9).

ConstructorDeclaration:
{ConstructorModifier} ConstructorDeclarator [Throws] ConstructorBody
ConstructorDeclarator:
[TypeParameters] SimpleTypeName
( [ReceiverParameter ,] [FormalParameterList] )
SimpleTypeName:
TypeIdentifier

The rules in this section apply to constructors in all class declarations, including enum declarations and record declarations. However, special rules apply to enum declarations with regard to constructor modifiers, constructor bodies, and default constructors; these rules are stated in 8.9.2. Special rules also apply to record declarations with regard to constructors, including a special compact declaration form; the details are given in 8.10.4.

The SimpleTypeName in the ConstructorDeclarator must be the simple name of the class that contains the constructor declaration, or a compile-time error occurs.

In all other respects, a constructor declaration looks just like a method declaration that has no result (8.4.5).

Constructor declarations are not members. They are never inherited and therefore are not subject to hiding or overriding.

Constructors are invoked by class instance creation expressions (15.9), by the conversions and concatenations caused by the string concatenation operator + (15.18.1), and by explicit constructor invocations from other constructors (8.8.7). Access to constructors is governed by access modifiers (6.6), so it is possible to prevent class instantiation by declaring an inaccessible constructor (8.8.10).

Constructors are never invoked by method invocation expressions (15.12).

Example 8.8-1. Constructor Declarations

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}

8.10 Record Declarations

A record declaration specifies a new record class, a special kind of class that defines a simple aggregate of values.

RecordDeclaration:
{ClassModifier} record TypeIdentifier [TypeParameters] RecordHeader [ClassImplements] RecordBody

The TypeIdentifier in the record declaration specifies the name of the record class.

A record declaration may specify a top level record class (7.6), a member record class (8.5, 9.5), or a local record class (14.3).

It is a compile-time error if a record declaration has the modifier abstract.

A record declaration is implicitly final. It is permitted for the declaration of a record class to redundantly specify the final modifier.

JEP 360 proposes extending Java to support sealed classes. In this case, the following text would be added:

It is a compile-time error if a record declaration has the modifier sealed.

A nested record declaration is implicitly static. It is permitted for the declaration of a member record class to redundantly specify the static modifier. A local record declaration may not redundantly specify the static modifier (14.3).

It is a compile-time error if the same keyword appears more than once as a modifier for a record declaration, or if a record declaration has more than one of the access modifiers public, protected, and private (6.6).

The direct superclass type of a record class is Record (8.1.4).

A record declaration has no extends clause, so it is not possible to explicitly declare that the direct superclass type of a record class is Record.

The serialization mechanism treats instances of a record class differently than ordinary serializable or externalizable objects. In particular, a record object is deserialized using the canonical constructor (8.10.4).

8.10.1 Record Components

The header of a record declaration consists of a record component list, which is a possibly empty list of record components. Each record component consists of a type (optionally preceded by one or more annotations) and an identifier that specifies the name of the record component. If a record class has no record components, then the record header consists of an empty pair of parentheses.

Each record component corresponds to an implicitly declared field and an accessor method (declared either explicitly or implicitly) of the record class (8.10.3).

RecordHeader:
( [ RecordComponentList ] )
RecordComponentList:
RecordComponent { , RecordComponent }
RecordComponent:
{ Annotation } UnannType Identifier
VariableArityRecordComponent
VariableArityRecordComponent:
{ Annotation } UnannType { Annotation } ... Identifier

A record component may be a variable arity record component, indicated by an ellipsis following the type. At most one variable arity record component is permitted for a record type. It is a compile-time error if a variable arity record component appears anywhere in the list of record components except the last position.

It is a compile-time error for a record declaration to declare two record components with the same name.

It is a compile-time error for a record declaration to declare a record component with the name clone, finalize, getClass, hashCode, notify, notifyAll, toString, or wait (8.10.3).

The rules for annotation modifiers on a record component are specified in 9.7.4. Annotations on a record component of a record class may be propagated to members and constructors of the record class as specified in 8.10.3. An annotation on a record component only remains on the record component if its annotation type is applicable in the record component context (9.6.4.1).

The declared type of a record component depends on whether it is a variable arity record component:

If the declared type of a variable arity record component has a non-reifiable element type (4.7), then a compile-time unchecked warning occurs for the declaration of the variable arity record component, unless the canonical constructor (8.10.4) is annotated with @SafeVarargs (9.6.4.7) or the warning is suppressed by @SuppressWarnings (9.6.4.5).

8.10.2 Record Bodies

The body of a record declaration may contain constructor and member declarations as well as static initializers.

RecordBody:
{ {RecordBodyDeclaration} }
RecordBodyDeclaration:
ClassBodyDeclaration
CompactConstructorDeclaration

The following productions from 8.1.6 are shown here for convenience:

ClassBodyDeclaration:
ClassMemberDeclaration
InstanceInitializer
StaticInitializer
ConstructorDeclaration
ClassMemberDeclaration:
FieldDeclaration
MethodDeclaration
ClassDeclaration
InterfaceDeclaration
;

It is a compile-time error for the body of a record declaration to contain a non-static field declaration (8.3.1.1).

It is a compile-time error for the body of a record declaration to contain a native method declaration (8.4.3.4).

It is a compile-time error for the body of a record declaration to contain an instance initializer (8.6).

8.10.3 Record Members

A record class has for each record component appearing in the record component list an implicitly declared field with the same name as the record component and the same type as the declared type of the record component. This field is declared private and final. The field is annotated with the annotations, if any, that appear on the corresponding record component and whose annotation types are applicable in the field declaration context, or in type contexts, or both.

In a record declaration, an accessor method for a record component is a method whose name is the same as the name of the given record component, and whose formal parameter list is empty.

If an accessor method for a record component is declared explicitly, then it must satisfy the following:

Otherwise, a compile-time error occurs.

If an accessor method for a record component is not explicitly declared, then one is implicitly declared with the following properties:

An implicitly declared accessor method must satisfy all the rules for a method in a normal class declaration (8.4).

A record class will thus have accessor methods for all record components appearing in the record component list.

Annotations that appear on a record component are not propagated to an explicitly declared accessor method for that record component. This is in contrast to an implicitly declared accessor method which is annotated with the applicable annotations from the corresponding record component.

Annotations that are propagated to an implicitly declared accessor method must result in a correctly annotated method. For example, in the following program, the implicitly declared accessor method would be annotated with the @SafeVarargs annotation, but this method is not correctly annotated as it is a fixed arity method (9.6.4.7).

    record BadRecord(@SafeVarargs int x) {}  // Error!

An accessor method (explicitly or implicitly declared) may override or overload methods declared in superinterfaces of the record class.

The restrictions on the record component names (8.10.1) mean that no implicitly declared accessor method will have a signature that is override-equivalent with a non-private method of the class Object.

All record classes provide an implementation of the abstract methods declared in the class Record. For each of the following methods, if a record class does not explicitly declare a method with the same signature (8.4.2), then the method is declared implicitly:

Record classes may declare or inherit other members, as described in 8.2. All members of record classes, including the implicitly declared members, are subject to the usual rules for member declarations in a class (8.3, 8.4, 8.5).

For example, a record class can inherit default methods from its direct superinterfaces. Given the declarations:

interface Logging{
    default void logAction() { ... }
}

record Point(int i, int j) implements Logging {}

Then the following code works as expected:

Point p = new Point(42,37);
p.logAction();

8.10.4 Record Constructor Declarations

To support proper initialization of its record components, a record class does not implicitly declare a default constructor (8.8.9). Instead, a record class has a canonical constructor, declared either explicitly or implicitly, that initializes all of the fields corresponding to the record components.

A record class R has a derived constructor signature that consists of the name R, no type parameters, and the formal parameter types derived from the record component list of R by taking the declared type of each record component.

A record class R has a derived formal parameter list that is constructed by taking each record component in the record component list and deriving a formal parameter with the same name and the declared type of the record component.

There are two ways to explicitly declare a canonical constructor in a record declaration: either by declaring a constructor with a particular signature, or by declaring a compact constructor.

A constructor in a declaration of a record class R is said to be a canonical constructor if its signature is override-equivalent (8.4.2) to the derived constructor signature of R.

As a canonical constructor has a signature that is override-equivalent to the derived constructor signature of a record class, there can only be one explicitly declared canonical constructor.

The access modifier of a canonical constructor for a record class must provide at least as much access as the record class, as follows:

A constructor declaration that is not a compact constructor but is a canonical constructor must satisfy the following conditions:

Otherwise, a compile-time error occurs.

A consequence of these rules is that that the annotations on a record component can differ from the annotations on the corresponding formal parameter of an explicitly declared canonical constructor. For example, the following is valid:

  @Target(ElementType.TYPE_USE)
  @interface DevAnnotation{ String value(); }

  record R(@DevAnnotation("devA") String s) {
      R(@DevAnnotation("devB") String s) {
          // Explicitly declared canonical constructor
          ...
      }
  }

The second way to explicitly declare a canonical constructor in a record declaration is to provide a compact constructor declaration, which is a special, succinct form of constructor declaration only available in a record declaration.

CompactConstructorDeclaration:
{ ConstructorModifier } SimpleTypeName ConstructorBody

In a record class R, the formal parameter list for a compact constructor declaration is implicitly declared and given by the derived formal parameter list of R.

Thus, given a record declaration with a record component named c and a compact constructor declaration, in the body of the compact constructor an occurrence of an unqualified name c denotes the implicit formal parameter c.

In a record class R, the signature of a compact constructor declaration is the derived constructor signature of R.

It is a compile-time error to declare more than one compact constructor in a record class.

The rules concerning signatures of constructors in class declarations (8.8.2) mean that it is also a compile-time error if a record declaration contains a compact constructor declaration and a standard constructor declaration that is a canonical constructor. A compact constructor is a canonical constructor.

A compact constructor declaration must satisfy all of the following conditions; otherwise a compile-time error occurs.

It is a compile-time error if an assignment occurs (16) to a field corresponding to a record component of the record class in the body of the compact constructor.

All fields corresponding to the record components of the record class are implicitly initialized to the value of the corresponding formal parameter after the body of the compact constructor. These fields are implicitly initialized in the order that they are declared in the record component list.

The intention of a compact constructor declaration is that only validation and/or normalization code need be given in the constructor body; the remaining initialization code is supplied by the compiler. Here is a simple example:

record Rational(int num, int denom) { 
    Rational {
        int gcd = gcd(num, denom);
        num /= gcd;
        denom /= gcd;
    }
}

This declaration is equivalent to the following declaration:

record Rational(int num, int denom) { 
    Rational(int num, int demon) {
        int gcd = gcd(num, denom);
        num /= gcd;
        denom /= gcd;
        this.num = num;
        this.denom = denom;
    }
}

In the declaration of a record class R, if a canonical constructor is not explicitly declared, then one is implicitly declared with the following properties:

An implicitly declared canonical constructor must satisfy all the rules for constructor declarations in a normal class declaration (8.8).

If in a declaration of a record class R there all other declarations of constructors that are not canonical constructors (if any) must satisfy the following:

Otherwise, a compile-time error occurs.

8.10.5 Record Declarations and Annotations

Record components in a record declaration may be annotated. These annotations are propagated to the (implicit and explicit) record member declarations and constructor declarations as defined in 8.10.3 and 8.10.4.

However, a subtle interaction between this process of propagation, the context in which the annotation is applicable, and any explicit declarations in the body of the record declaration could result in an annotation inadvertently being lost.

The following example highlights such a case. The record component is annotated with an annotation that is only applicable in the method context. This means that it is propagated to the corresponding accessor method. However, as the accessor method has been explicitly declared it is not propagated.

  @Target(ElementType.METHOD)
  @interface A { }

  record R1(@A int x) {     // Where did the annotation go?
      int x() { return x; }
  }

Another example is where an annotation is applicable in the formal parameter context. This means that it is propagated the canonical constructor. However, as the canonical constructor has been explicitly declared, it is not propagated.

  @Target(ElementType.PARAMETER)
  @interface B { }

  record R2(@B String s) {     // Where did the annotation go?
      R(String s) {
          // Explicitly declared canonical constructor
          ...
      }
  }

An annotation that annotates a record component that is applicable in the method context is not propagated if the accessor method corresponding to the record component is declared explicitly.

An annotation that annotates a record component that is applicable in the parameter context is not propagated if a canonical constructor is declared explicitly.

In all other cases, the annotation that annotates a record component is propagated.

An annotation that annotates an record component may be applicable in many contexts. An annotation for a record component is said to be discarded if for every context that it is applicable, the annotation is not propagated.

It is a compile-time error if a record class declares a record component that is annotated with at least one annotation that is discarded.

The quantification in the definition of a discarded annotation is important. The following example has an annotation that is applicable in both the method and parameter contexts.

  @Target({ElementType.METHOD, ElementType.PARAMETER})
  @interface C { }

  record R3(@C String s) {     // Error - Annotation is lost!
    R3(String s) {
       this.s = s;
    }
    public String s() { return s; }
  }

The following example has an annotation that is not propagated to the accessor method but is not discarded by virtue of being propagated to the private field.

  @Target({ElementType.METHOD, ElementType.FIELD})
  @interface D { }

  record R4 (@D String s) {     // Ok
    public String s() { return s; }
  }

Chapter 9: Interfaces

9.6 Annotation Interfaces

9.6.4 Predefined Annotation Interfaces

9.6.4.1 @Target

An annotation of type java.lang.annotation.Target is used on the declaration of an annotation interface T to specify the contexts in which T is applicable. java.lang.annotation.Target has a single element, value, of type java.lang.annotation.ElementType[], to specify contexts.

Annotation interfaces may be applicable in declaration contexts, where annotations apply to declarations, or in type contexts, where annotations apply to types used in declarations and expressions.

There are nine ten declaration contexts, each corresponding to an enum constant of java.lang.annotation.ElementType:

  1. Module declarations (7.7)

    Corresponds to java.lang.annotation.ElementType.MODULE

  2. Package declarations (7.4.1)

    Corresponds to java.lang.annotation.ElementType.PACKAGE

  3. Type declarations: class, interface, enum, record, and annotation declarations (8.1.1, 9.1.1, 8.5, 9.5, 8.9, 8.10, 9.6)

    Corresponds to java.lang.annotation.ElementType.TYPE

    Additionally, annotation declarations correspond to java.lang.annotation.ElementType.ANNOTATION_TYPE

  4. Method declarations (including elements of annotation interfaces) (8.4.3, 9.4, 9.6.1)

    Corresponds to java.lang.annotation.ElementType.METHOD

  5. Constructor declarations (8.8.3)

    Corresponds to java.lang.annotation.ElementType.CONSTRUCTOR

  6. Type parameter declarations of generic classes, interfaces, methods, and constructors (8.1.2, 9.1.2, 8.4.4, 8.8.4)

    Corresponds to java.lang.annotation.ElementType.TYPE_PARAMETER

  7. Field declarations (including enum constants) (8.3.1, 9.3, 8.9.1)

    Corresponds to java.lang.annotation.ElementType.FIELD

  8. Formal and exception parameter declarations (8.4.1, 9.4, 14.20)

    Corresponds to java.lang.annotation.ElementType.PARAMETER

  9. Local variable declarations (including loop variables of for statements and resource variables of try-with-resources statements) (14.4, 14.14.1, 14.14.2, 14.20.3)

    Corresponds to java.lang.annotation.ElementType.LOCAL_VARIABLE

  10. Record component declarations (8.10.1)

    Corresponds to java.lang.annotation.ElementType.RECORD_COMPONENT

There are 16 17 type contexts (4.11), all represented by the enum constant TYPE_USE of java.lang.annotation.ElementType.

It is a compile-time error if the same enum constant appears more than once in the value element of an annotation of type java.lang.annotation.Target.

If an annotation of type java.lang.annotation.Target is not present on the declaration of an annotation interface T, then T is applicable in all nine ten declaration contexts and in all 16 17 type contexts.

9.6.4.4 @Override

Programmers occasionally overload a method declaration when they mean to override it, leading to subtle problems. The annotation interface Override supports early detection of such problems.

The classic example concerns the equals method. Programmers write the following in class Foo:

public boolean equals(Foo that) { ... }

when they mean to write:

public boolean equals(Object that) { ... }

This is perfectly legal, but class Foo inherits the equals implementation from Object, which can cause some subtle bugs.

If a method declaration in class or interface T is annotated with @Override, but the method does not override from T a method declared in a supertype of T (8.4.8.1, 9.4.1.1), or is not override-equivalent to a public method of Object (4.3.2, 8.4.2), then a compile-time error occurs.

It is a compile-time error for a method declaration in a class or interface T to be annotated with @Override unless one of the following conditions holds:

This behavior differs from Java SE 5.0, where @Override only caused a compile-time error if applied to a method that implemented a method from a superinterface that was not also present in a superclass.

The clause about overriding a public method is motivated by use of @Override in an interface. Consider the following declarations:

class Foo     { @Override public int hashCode() {..} }
interface Bar { @Override int hashCode(); }

The use of @Override in the class declaration is legal by the first clause, because Foo.hashCode overrides from Foo the method Object.hashCode.

For the interface declaration, consider that while an interface does not have Object as a supertype, an interface does have public abstract members that correspond to the public members of Object (9.2). If an interface chooses to declare them explicitly (that is, to declare members that are override-equivalent to public methods of Object), then the interface is deemed to override them, and use of @Override is allowed.

However, consider an interface that attempts to use @Override on a clone method: (finalize could also be used in this example)

interface Quux { @Override Object clone(); }

Because Object.clone is not public, there is no member called clone implicitly declared in Quux. Therefore, the explicit declaration of clone in Quux is not deemed to "implement" any other method, and it is erroneous to use @Override. (The fact that Quux.clone is public is not relevant.)

In contrast, a class declaration that declares clone is simply overriding Object.clone, so is able to use @Override:

class Beep { @Override protected Object clone() {..} }

The @Override annotation has a special meaning in a record declaration, where it can be used to specify that a method declaration is an accessor method (8.10.3) for a record component. Consider the following declaration:

record R(int x) {
   @Override
   public int x() { 
       return Math.abs(x);
   }
   ...
}

The @Override annotation on the accessor method x ensures that if the record component x is modified or removed, then the corresponding accessor method must be modified or removed too.

9.7 Annotations

9.7.4 Where Annotations May Appear

A declaration annotation is an annotation that applies to a declaration, and whose annotation interface is applicable in the declaration context (9.6.4.1) represented by that declaration; or an annotation that applies to a class, interface, or type parameter declaration, and whose annotation interface is applicable in type contexts (4.11).

A type annotation is an annotation that applies to a type (or any part of a type), and whose annotation interface is applicable in type contexts.

For example, given the field declaration:

@Foo int f;

@Foo is a declaration annotation on f if Foo is meta-annotated by @Target(ElementType.FIELD), and a type annotation on int if Foo is meta-annotated by @Target(ElementType.TYPE_USE). It is possible for @Foo to be both a declaration annotation and a type annotation simultaneously.

Type annotations can apply to an array type or any component type thereof (10.1). For example, assuming that A, B, and C are annotation interfaces meta-annotated with @Target(ElementType.TYPE_USE), then given the field declaration:

@C int @A [] @B [] f;

@A applies to the array type int[][], @B applies to its component type int[], and @C applies to the element type int. For more examples, see 10.2.

An important property of this syntax is that, in two declarations that differ only in the number of array levels, the annotations to the left of the type refer to the same type. For example, @C applies to the type int in all of the following declarations:

@C int f;
@C int[] f;
@C int[][] f;

It is customary, though not required, to write declaration annotations before all other modifiers, and type annotations immediately before the type to which they apply.

It is possible for an annotation to appear at a syntactic location in a program where it could plausibly apply to a declaration, or a type, or both. This can happen in any of the five six declaration contexts where modifiers immediately precede the type of the declared entity:

The grammar of the Java programming language unambiguously treats annotations at these locations as modifiers for a declaration (8.3), but that is purely a syntactic matter. Whether an annotation applies to the declaration or to the type of the declared entity - and thus, whether the annotation is a declaration annotation or a type annotation - depends on the applicability of the annotation's interface:

In the second and third cases above, the type which is closest to the annotation is determined as follows:

It is a compile-time error if an annotation of interface A is syntactically a modifier for:

Five Six of these nine eleven clauses mention "... or type contexts" because they characterize the five six syntactic locations where an annotation could plausibly apply either to a declaration or to the type of a declared entity. Furthermore, two of the nine eleven clauses - for class and interface declarations, and for type parameter declarations - mention "... or type contexts" because it may be convenient to apply an annotation whose interface is meta-annotated with @Target(ElementType.TYPE_USE) (thus, applicable in type contexts) to a class, interface, or type parameter declaration.

A type annotation is admissible if both of the following are true:

The intuition behind the second clause is that if Outer.this is legal in a nested class enclosed by Outer, then Outer may be annotated because it represents the type of some object at run time. On the other hand, if Outer.this is not legal - because the class where it appears has no enclosing instance of Outer at run time - then Outer may not be annotated because it is logically just a name, akin to components of a package name in a fully qualified type name.

For example, in the following program, it is not possible to write A.this in the body of B, as B has no lexically enclosing instances (8.5.1). Therefore, it is not possible to apply @Foo to A in the type A.B, because A is logically just a name, not a type.

@Target(ElementType.TYPE_USE)
@interface Foo {}

class Test {
  class A {
    static class B {}
  }

  @Foo A.B x;  // Illegal 
}

On the other hand, in the following program, it is possible to write C.this in the body of D. Therefore, it is possible to apply @Foo to C in the type C.D, because C represents the type of some object at run time.

@Target(ElementType.TYPE_USE)
@interface Foo {}

class Test {
  static class C {
    class D {}
  }

  @Foo C.D x;  // Legal 
}

Finally, note that the second clause looks only one level deeper in a qualified type. This is because a static class may only be nested in a top level class or another static nested class. It is not possible to write a nest like:

@Target(ElementType.TYPE_USE)
@interface Foo {}

class Test {
  class E {
    class F {
      static class G {}
    }
  }

  @Foo E.F.G x;
}

Assume for a moment that the nest was legal. In the type of field x, E and F would logically be names qualifying G, as E.F.this would be illegal in the body of G. Then, @Foo should not be legal next to E. Technically, however, @Foo would be admissible next to E because the next deepest term F denotes an inner class; but this is moot as the class nest is illegal in the first place.

It is a compile-time error if an annotation of interface A applies to the outermost level of a type in a type context, and A is not applicable in type contexts or the declaration context (if any) which occupies the same syntactic location.

It is a compile-time error if an annotation of interface A applies to a part of a type (that is, not the outermost level) in a type context, and A is not applicable in type contexts.

It is a compile-time error if an annotation of interface A applies to a type (or any part of a type) in a type context, and A is applicable in type contexts, but the annotation is not admissible.

For example, assume an annotation interface TA which is meta-annotated with just @Target(ElementType.TYPE_USE). The terms @TA java.lang.Object and java.@TA lang.Object are illegal because the simple name to which @TA is closest is classified as a package name. On the other hand, java.lang.@TA Object is legal.

Note that the illegal terms are illegal "everywhere". The ban on annotating package names applies broadly: to locations which are solely type contexts, such as class ... extends @TA java.lang.Object {...}, and to locations which are both declaration and type contexts, such as @TA java.lang.Object f;. (There are no locations which are solely declaration contexts where a package name could be annotated, as class, package, and type parameter declarations use only simple names.)

If TA is additionally meta-annotated with @Target(ElementType.FIELD), then the term @TA java.lang.Object is legal in locations which are both declaration and type contexts, such as a field declaration @TA java.lang.Object f;. Here, @TA is deemed to apply to the declaration of f (and not to the type java.lang.Object) because TA is applicable in the field declaration context.

Chapter 10: Arrays

10.2 Array Variables

A variable of array type holds a reference to an object. Declaring a variable of array type does not create an array object or allocate any space for array components. It creates only the variable itself, which can contain a reference to an array. However, the initializer part of a declarator (8.3, 9.3, 14.4.1) may create an array, a reference to which then becomes the initial value of the variable.

Example 10.2-1. Declarations of Array Variables

int[]     ai;        // array of int
short[][] as;        // array of array of short
short     s,         // scalar short
          aas[][];   // array of array of short
Object[]  ao,        // array of Object
          otherAo;   // array of Object
Collection<?>[] ca;  // array of Collection of unknown type

The declarations above do not create array objects. The following are examples of declarations of array variables that do create array objects:

Exception ae[]  = new Exception[3];
Object aao[][]  = new Exception[2][3];
int[] factorial = { 1, 1, 2, 6, 24, 120, 720, 5040 };
char ac[]       = { 'n', 'o', 't', ' ', 'a', ' ',
                    'S', 't', 'r', 'i', 'n', 'g' };
String[] aas    = { "array", "of", "String", };

The array type of a variable depends on the bracket pairs that may appear as part of the type at the beginning of a variable declaration, or as part of the declarator for the variable, or both. Specifically, in the declaration of a field, formal parameter, or local variable, or record component (8.3, 8.4.1, 9.3, 9.4, 14.4.1, 14.14.2, 15.27.1, 8.10.1), the array type of the variable is denoted by:

The return type of a method (8.4.5) may be an array type. The precise array type depends on the bracket pairs that may appear as part of the type at the beginning of the method declaration, or after the method's formal parameter list, or both. The array type is denoted by:

We do not recommend "mixed notation" in array variable declarations, where bracket pairs appear on both the type and in declarators; nor in method declarations, where bracket pairs appear both before and after the formal parameter list.

Example 10.2-2. Array Variables and Array Types

The local variable declaration statement:

byte[] rowvector, colvector, matrix[];

is equivalent to:

byte rowvector[], colvector[], matrix[][];

because the array type of each local variable is unchanged. Similarly, the local variable declaration statement:

int a, b[], c[][];

is equivalent to the series of declaration statements:

int a;
int[] b;
int[][] c;

Brackets are allowed in declarators as a nod to the tradition of C and C++. The general rules for variable declaration, however, permit brackets to appear on both the type and in declarators, so that the local variable declaration statement:

float[][] f[][], g[][][], h[];  // Yechh!

is equivalent to the series of declarations:

float[][][][] f;
float[][][][][] g;
float[][][] h;

Because of how array types are formed, the following parameter declarations have the same array type:

void m(int @A [] @B []  x) {}
void n(int @A [] @B ... y) {}

And perhaps surprisingly, the following field declarations have the same array type:

int @A [] f @B [];
int @B [] @A [] g;

Once an array object is created, its length never changes. To make an array variable refer to an array of different length, a reference to a different array must be assigned to the variable.

A single variable of array type may contain references to arrays of different lengths, because an array's length is not part of its type.

If an array variable v has type A[], where A is a reference type, then v can hold a reference to an instance of any array type B[], provided B can be assigned to A (5.2). This may result in a run-time exception on a later assignment; see 10.5 for a discussion.

Chapter 13: Binary Compatibility

13.1 The Form of a Binary

A companion document describes the changes needed to the Java Virtual Machine Specification to support records.

...

A binary representation for a class or interface must also contain all of the following:

  1. If it is a class and is not Object, then a symbolic reference to the direct superclass of this class.

  2. A symbolic reference to each direct superinterface, if any.

  3. A specification of each field declared in the class or interface, given as the simple name of the field and a symbolic reference to the erasure of the type of the field.

  4. If it is a class, then the erased signature of each constructor, as described above.

  5. For each method declared in the class or interface (excluding, for an interface, its implicitly declared methods (9.2)), its erased signature and return type, as described above.

  6. The code needed to implement the class or interface:

    • For an interface, code for the field initializers and the implementation of each method with a block body (9.4.3).

    • For a class, code for the field initializers, the instance and static initializers, the implementation of each method with a block body (8.4.7), and the implementation of each constructor.

  7. Every class or interface must contain sufficient information to recover its canonical name (6.7).

  8. Every member class or interface must have sufficient information to recover its source-level access modifier.

  9. Every nested class or interface must have a symbolic reference to its immediately enclosing class or interface (8.1.3).

  10. Every class or interface must contain symbolic references to all of its member classes and interfaces (8.5, 9.5), and to all other nested classes and interfaces declared within its body.

  11. A construct emitted by a Java compiler must be marked as synthetic if it does not correspond to a construct declared explicitly or implicitly in source code, unless the emitted construct is a class initialization method (JVMS §2.9).

  12. A construct emitted by a Java compiler must be marked as mandated if it corresponds to a formal parameter declared implicitly in source code (8.8.1, 8.8.9, 8.9.3, 15.9.5.1).

The following formal parameters are declared implicitly in source code:

For reference, the following constructs are declared implicitly in source code, but are not marked as mandated because only formal parameters can be so marked in a class file (JVMS §4.7.24):

...

13.4 Evolution of Classes

13.4.27 Evolution of Record Classes

Adding, deleting, changing, or reordering record components in a record class declaration may break compatibility with pre-existing binaries that are not recompiled; such a change is not recommended for widely distributed record classes.

More precisely, adding, deleting, changing, or reordering record components may change the corresponding implicit field declarations and corresponding accessor method declarations, as well as changing the signature and implementation of the canonical constructor and other supporting methods, with consequences described in 13.4.8 and 13.4.12.

In all other respects, the binary compatibility rules for record classes are identical to those for normal classes.

Chapter 14: Blocks and Statements

14.3 Local Class and Interface Declarations

A local class or a local interface is a nested class or interface (8, 9) whose declaration is immediately contained by a block (14.2).

LocalClassOrInterfaceDeclaration:
ClassDeclaration
NormalInterfaceDeclaration

A local class may be an enum class (8.9) or a record class (8.10). A local interface may not be an annotation interface (9.6).

Local class and interface declarations may be intermixed freely with statements in the block.

A local class or interface is not a member of any package, class, or interface. Unlike an anonymous class (15.9.5), a local class or interface has a simple name (6.2, 6.7).

Local enum classes, record classes, and local interfaces are implicitly static (8.1.1.4, 9.1.1.3). A local class that is not implicitly static is an inner class (8.1.3).

It is a compile-time error if a local class or interface is declared with any of the access modifiers public, protected, or private (6.6), or the modifier static (8.1.1).

The scope and shadowing of a local class or interface declaration is specified in 6.3 and 6.4.

Example 14.3-1. Local Class and Interface Declarations

Here is an example that illustrates several aspects of the rules given above:

class Global {
    class Cyclic {}

    void foo() {
        new Cyclic(); // create a Global.Cyclic
        class Cyclic extends Cyclic {} // circular definition

        {
            class Local {}
            {
                class Local {} // compile-time error
            }
            class Local {} // compile-time error
            class AnotherLocal {
                void bar() {
                    class Local {} // ok
                }
            }
        }
        class Local {} // ok, not in scope of prior Local
    }
}

The first statement of method foo creates an instance of the member class Global.Cyclic rather than an instance of the local class Cyclic, because the statement appears prior to the scope of the local class declaration.

The fact that the scope of a local class declaration encompasses its whole declaration (not only its body) means that the definition of the local class Cyclic is indeed cyclic because it extends itself rather than Global.Cyclic. Consequently, the declaration of the local class Cyclic is rejected at compile time.

Since local class names cannot be redeclared within the same method (or constructor or initializer, as the case may be), the second and third declarations of Local result in compile-time errors. However, Local can be redeclared in the context of another, more deeply nested, class such as AnotherLocal.

The final declaration of Local is legal, since it occurs outside the scope of any prior declaration of Local.

Chapter 16: Definite Assignment

Each local variable (14.4) and every blank final field ([4.12.4], 8.3.1.2) must have a definitely assigned value when any access of its value occurs.

An access to its value consists of the simple name of the variable (or, for a field, the simple name of the field qualified by this) occurring anywhere in an expression except as the left-hand operand of the simple assignment operator = ([15.26.1]).

For every access of a local variable or blank final field x, x must be definitely assigned before the access, or a compile-time error occurs.

Similarly, every blank final variable must be assigned at most once; it must be definitely unassigned when an assignment to it occurs.

Such an assignment is defined to occur occur if and only if either the simple name of the variable (or, for a field, its simple name qualified by this) occurs on the left hand side of an assignment operator.

This is an editorial change to italicize the notion of an assignment occurring.

...