Value Classes and Objects

Changes to the Java® Language Specification • Version 24-internal-2024-11-09-0040456.dlsmith...

This document describes changes to the Java Language Specification, Java SE 23 Edition, as modified by Flexible Constructor Bodies, to support value classes and objects, a preview feature introduced by JEP 401.

Related documents: JVM ChangesFlexible Constructor Bodies

Key changes include:

Changes are described with respect to existing sections of the JVM 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.

Revision history:

Chapter 1: Introduction

1.5 Preview Features

A preview feature is:

that is fully specified, fully implemented, and yet impermanent. It is available in implementations of a given release of the Java SE Platform to provoke developer feedback based on real world use; this may lead to it becoming permanent in a future release of the Java SE Platform.

Implementations must disable, at both compile time and run time, the preview features defined by a given release of the Java SE Platform, unless the user indicates via the host system, at both compile time and run time, that preview features are to be enabled.

The preview features defined by a given release of the Java SE Platform are enumerated in the Java SE Platform Specification for that release. The preview features are specified as follows:

The rules for use of preview language features are as follows:

Some preview APIs are described as reflective by the Java SE Platform Specification, principally in the java.lang.reflect, java.lang.invoke, and javax.lang.model packages. The rule for use of reflective preview APIs is as follows:

All preview APIs not described as reflective in the Java SE Platform Specification are normal. The rules for use of normal preview APIs are as follows:

Chapter 3: Lexical Structure

3.8 Identifiers

...

To facilitate the recognition of contextual keywords, the syntactic grammar (2.3) sometimes disallows certain identifiers by defining a production to accept only a subset of identifiers. The subsets are as follows:

TypeIdentifier:
Identifier but not permits, record, sealed,
value, var, or yield
UnqualifiedMethodIdentifier:
Identifier but not yield

TypeIdentifier is used in the declaration of classes, interfaces, and type parameters (8.1, 9.1, 4.4), and when referring to types (6.5). For example, the name of a class must be a TypeIdentifier, so it is illegal to declare a class named permits, record, sealed, value, var, or yield.

UnqualifiedMethodIdentifier is used when a method invocation expression refers to a method by its simple name (6.5.7.1). Since the term yield is excluded from UnqualifiedMethodIdentifier, any invocation of a method named yield must be qualified, thus distinguishing the invocation from a yield statement (14.21).

For the purpose of parsing, modifiers in class headers don't actually conflict with types, and so it would be possible to to recognize the value keyword (along with sealed) without disallowing it as a class name.

However, it's potentially convenient to be able to implement a single keyword recognition algorithm for modifiers on all declarations, and in that approach it's impossible to distinguish between a constructor declaration or a method declaration spelled value foo() {} (that is, unless the algorithm is willing to rely on name resolution to decide whether foo is the current class name). So we've tentatively added value to the list of names excluded from TypeIdentifier.

3.9 Keywords

51 character sequences, formed from ASCII characters, are reserved for use as keywords and cannot be used as identifiers (3.8). Another 17 18 character sequences, also formed from ASCII characters, may be interpreted as keywords or as other tokens, depending on the context in which they appear.

Keyword:
ReservedKeyword
ContextualKeyword
ReservedKeyword:
(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)
ContextualKeyword:
(one of)
exports opens requires uses yield
module permits sealed var
non-sealed provides to when
open record transitive with
value

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 strictfp is obsolete and should not be used in new code.

The keyword _ (underscore) may be used in certain declarations in place of an identifier ([6.1]).

true and false are not keywords, but rather boolean literals (3.10.3).

null is not a keyword, but rather the null literal (3.10.8).

During the reduction of input characters to input elements (3.5), a sequence of input characters that notionally matches a contextual keyword is reduced to a contextual keyword if and only if both of the following conditions hold:

  1. The sequence is recognized as a terminal specified in a suitable context of the syntactic grammar (2.3), as follows:

    • For module and open, when recognized as a terminal in a ModuleDeclaration (7.7).

    • For exports, opens, provides, requires, to, uses, and with, when recognized as a terminal in a ModuleDirective.

    • For transitive, when recognized as a terminal in a RequiresModifier.

      For example, recognizing the sequence requires transitive ; does not make use of RequiresModifier, so the term transitive is reduced here to an identifier and not a contextual keyword.

    • For var, when recognized as a terminal in a LocalVariableType (14.4) or a LambdaParameterType (15.27.1).

      In other contexts, attempting to use var as an identifier will cause an error, because var is not a TypeIdentifier (3.8).

    • For yield, when recognized as a terminal in a YieldStatement (14.21).

      In other contexts, attempting to use the yield as an identifier will cause an error, because yield is neither a TypeIdentifier nor a UnqualifiedMethodIdentifier.

    • For record, when recognized as a terminal in a RecordDeclaration (8.10).

    • For permits, when recognized as a terminal in a ClassPermits (8.1.6) or an InterfacePermits (9.1.4).

    • For non-sealed, permits, and sealed, when recognized as a terminal ClassModifier or InterfaceModifier in a NormalClassDeclaration (8.1), EnumDeclaration (8.9), RecordDeclaration (8.10), or a NormalInterfaceDeclaration (9.1), or AnnotationInterfaceDeclaration (9.6).

    • For when, when recognized as a terminal in a Guard (14.11.1).

    • For value, when recognized as a ClassModifier in a NormalClassDeclaration, EnumDeclaration, or RecordDeclaration.

    Some combinations of modifiers and declarations don't make sense, like a value enum. However, these are semantic restrictions that should not affect parsing.

  2. The sequence is not immediately preceded or immediately followed by an input character that matches JavaLetterOrDigit.

In general, accidentally omitting white space in source code will cause a sequence of input characters to be tokenized as an identifier, due to the "longest possible translation" rule (3.2). For example, the sequence of twelve input characters p u b l i c s t a t i c is always tokenized as the identifier publicstatic, rather than as the reserved keywords public and static. If two tokens are intended, they must be separated by white space or a comment.

The rule above works in tandem with the "longest possible translation" rule to produce an intuitive result in contexts where contextual keywords may appear. For example, the sequence of eleven input characters v a r f i l e n a m e is usually tokenized as the identifier varfilename, but in a local variable declaration, the first three input characters are tentatively recognized as the contextual keyword var by the first condition of the rule above. However, it would be confusing to overlook the lack of white space in the sequence by recognizing the next eight input characters as the identifier filename. (This would mean that the sequence undergoes different tokenization in different contexts: an identifier in most contexts, but a contextual keyword and an identifier in local variable declarations.) Accordingly, the second condition prevents recognition of the contextual keyword var on the grounds that the immediately following input character f is a JavaLetterOrDigit. The sequence v a r f i l e n a m e is therefore tokenized as the identifier varfilename in a local variable declaration.

As another example of the careful recognition of contextual keywords, consider the sequence of 15 input characters n o n - s e a l e d c l a s s. This sequence is usually translated to three tokens - the identifier non, the operator -, and the identifier sealedclass - but in a normal class declaration, where the first condition holds, the first ten input characters are tentatively recognized as the contextual keyword non-sealed. To avoid translating the sequence to two keyword tokens (non-sealed and class) rather than three non-keyword tokens, and to avoid rewarding the programmer for omitting white space before class, the second condition prevents recognition of the contextual keyword. The sequence n o n - s e a l e d c l a s s is therefore tokenized as three tokens in a class declaration.

In the rule above, the first condition depends on details of the syntactic grammar, but a compiler for the Java programming language can implement the rule without fully parsing the input program. For example, a heuristic could be used to track the contextual state of the tokenizer, as long as the heuristic guarantees that valid uses of contextual keywords are tokenized as keywords, and valid uses of identifiers are tokenized as identifiers. Alternatively, a compiler could always tokenize a contextual keyword as an identifier, leaving it to a later phase to recognize special uses of these identifiers.

Chapter 4: Types, Values, and Variables

4.3 Reference Types and Values

4.3.1 Objects

An object is a class instance or an array. A class instance may be an identity object or a value object; every array is an identity object.

The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.

A class instance is explicitly created by a class instance creation expression (15.9).

An array is explicitly created by an array creation expression (15.10.1).

Other expressions may implicitly create a class instance (12.5) or an array (10.6).

Creation of an identity object produces a unique object, distinct from any that has previously been created. Creation of a value object may result in an object that is the same as a previously-created object.

Two value objects are the same if they are instances of the same class and the values of their instance fields are the same. For primitive-typed instance fields, this means the field values are identical (in the case of a floating-point type, all bits of the value are compared, as if by first applying the Float.floatToRawIntBits or Double.doubleToRawLongBits operation). For reference-typed instance fields, this means the field values are both references to the same object (possibly applying this definition recursively), or are both null.

Example 4.3.1-1. Object Creation

class Point {
    int x, y;
    Point() { System.out.println("default"); }
    Point(int x, int y) { this.x = x; this.y = y; }

    /* A Point instance is explicitly created at
       class initialization time: */
    static Point origin = new Point(0,0);

    /* A String can be implicitly created
       by a + operator: */
    public String toString() { return "(" + x + "," + y + ")"; }
}

class Test {
    public static void main(String[] args) {
        /* A Point is explicitly created
           using newInstance: */
        Point p = null;
        try {
            p = (Point)Class.forName("Point").newInstance();
        } catch (Exception e) {
            System.out.println(e);
        }

        /* An array is implicitly created
           by an array initializer: */
        Point[] a = { new Point(0,0), new Point(1,1) };

        /* Strings are implicitly created
           by + operators: */
        System.out.println("p: " + p);
        System.out.println("a: { " + a[0] + ", " + a[1] + " }");

        /* An array is explicitly created
           by an array creation expression: */
        String[] sa = new String[2];
        sa[0] = "he"; sa[1] = "llo";
        System.out.println(sa[0] + sa[1]);
    }
}

This program produces the output:

default
p: (0,0)
a: { (0,0), (1,1) }
hello

Programs work with objects via reference values. Reference values (often just references) refer to objects, or may be the special null reference, which refers to no object.

The operators on references to objects are:

There may be many references to the same object. Most objects Identity objects often have mutable state, stored in the fields of objects that are instances of classes or in the variables that are the components of an array object. If two variables contain references to the same identity object, the state of the object can be modified using one variable's reference to the object, and then the altered state can be observed through the reference in the other variable.

Example 4.3.1-2. Primitive and Reference Identity

class Value { int val; }

class Test {
    public static void main(String[] args) {
        int i1 = 3;
        int i2 = i1;
        i2 = 4;
        System.out.print("i1==" + i1);
        System.out.println(" but i2==" + i2);
        Value v1 = new Value();
        v1.val = 5;
        Value v2 = v1;
        v2.val = 6;
        System.out.print("v1.val==" + v1.val);
        System.out.println(" and v2.val==" + v2.val);
    }
}

This program produces the output:

i1==3 but i2==4
v1.val==6 and v2.val==6

because v1.val and v2.val reference the same instance variable (4.12.3) in the one Value object created by the only new expression, while i1 and i2 are different variables.

These examples need to be updated.

There are a lot of problems in this second example that will need to be cleaned up. For example, the word "value" is confusing here.

Each identity object is associated with a monitor (17.1), which is used by synchronized methods (8.4.3) and the synchronized statement (14.19) to provide control over concurrent access to state by multiple threads (17).

Chapter 5: Conversions and Contexts

5.1 Kinds of Conversion

5.1.7 Boxing Conversion

Boxing conversion treats expressions of a primitive type as expressions of a corresponding reference type. Specifically, the following nine conversions are called the boxing conversions:

At run time, boxing conversion proceeds as follows:

If the value p being boxed is the result of evaluating a constant expression (15.29) of type boolean, byte, char, short, int, or long, and the result is true, false, a character in the range '\u0000' to '\u007f' inclusive, or an integer in the range -128 to 127 inclusive, then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.

Ideally, boxing a primitive value would always yield an identical reference. In practice, this may not be feasible using existing implementation techniques. The rule above is a pragmatic compromise, requiring that certain common values always be boxed into indistinguishable objects. The implementation may cache these, lazily or eagerly. For other values, the rule disallows any assumptions about the identity of the boxed values on the programmer's part. This allows (but does not require) sharing of some or all of these references.

This ensures that in most common cases, the behavior will be the desired one, without imposing an undue performance penalty, especially on small devices. Less memory-limited implementations might, for example, cache all char and short values, as well as int and long values in the range of -32K to +32K.

If a and b are variables storing the results of any two boxing conversions of p, then it is always the case that a.equals(b) and a == b.

A boxing conversion may result in an OutOfMemoryError if a new instance of one of the wrapper classes (Boolean, Byte, Character, Short, Integer, Long, Float, or Double) needs to be allocated and insufficient storage is available.

Chapter 8: Classes

8.1 Class Declarations

8.1.1 Class Modifiers

A class declaration may include class modifiers.

ClassModifier:
(one of)
Annotation public protected private
abstract static final sealed non-sealed strictfp
value

The rules concerning annotation modifiers for a class declaration are specified in 9.7.4 and 9.7.5.

The access modifier public (6.6) pertains only to top level classes (7.6) and member classes (8.5, 9.5), not to local classes (14.3) or anonymous classes (15.9.5).

The access modifiers protected and private pertain only to member classes.

The modifier static pertains only to member classes and local classes.

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

It is a compile-time error if a class declaration has more than one of the modifiers sealed, non-sealed, and final.

If two or more (distinct) class modifiers appear in a class declaration, then it is customary, though not required, that they appear in the order consistent with that shown above in the production for ClassModifier.

8.1.1.2 sealed, non-sealed, and final Classes

A class can be declared sealed if all its direct subclasses are known when the class is declared (8.1.6), and no other direct subclasses are desired or required.

Explicit and exhaustive control over a class's direct subclasses is useful when the class hierarchy is used to model the kinds of values in a domain, rather than as a mechanism for code inheritance and reuse. The direct subclasses may themselves be declared sealed in order to further control the class hierarchy.

A class can be declared final if its definition is complete and no subclasses are desired or required.

It is a compile-time error if a class is declared both final and abstract, because the implementation of such a class could never be completed (8.1.1.1).

Because a final class never has any subclasses, the methods of a final class are never overridden (8.4.8.1).

A class is freely extensible if its direct superclass is not sealed (8.1.4), and none of its direct superinterfaces are sealed ([8.1.5]), and it is neither sealed nor final itself.

A class that has a sealed direct superclass or a sealed direct superinterface is freely extensible if and only if it is declared non-sealed.

It is a compile-time error if a class has a sealed direct superclass or a sealed direct superinterface, and is not declared final, sealed, or non-sealed either explicitly or implicitly.

Thus, an effect of the sealed keyword is to force all direct subclasses to explicitly declare whether they are final, sealed, or non-sealed. This avoids accidentally exposing a sealed class hierarchy to unwanted subclassing.

An enum class is either implicitly final or implicitly sealed, so it can implement a sealed interface. Similarly, a record class is record classes and non-abstract value classes are implicitly final, so it they can also implement a sealed interface.

It is a compile-time error if a class is declared non-sealed but has neither a sealed direct superclass nor a sealed direct superinterface.

Thus, a subclass of a non-sealed class cannot itself be declared non-sealed.

8.1.1.5 value Classes

The value modifier specifies that a class does not depend on object identity to support unique instance creation, instance field mutation, or synchronization (4.3.1). All instances of a non-abstract value class are value objects.

A class without the value modifier is an identity class, and may depend on features associated with object identity. All instances of an identity class are identity objects. A value class may not extend an identity class, with the exception of the identity class Object (8.1.4).

If a value class is not abstract, it is implicitly final (8.1.1.2). It is permitted for the class declaration to redundantly specify the final modifier.

It is a compile-time error if a value class is not abstract but is declared with the modifier sealed or non-sealed.

Special restrictions apply to the field declarations (8.3.1.2), method declarations (8.4.3.6), and constructors (8.8.7) of a value class.

Previous iterations of this specification required an abstract value class to be stateless and have an empty constructor. This is no longer the case: an abstract value class may declare instance fields, may be an inner class, and may have instance initialization logic.

8.1.4 Superclasses and Subclasses

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

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 (6.6), or a compile-time error occurs.

It is a compile-time error if the ClassType names a class that is sealed (8.1.1.2) and the class being declared is not a permitted direct subclass of the named class (8.1.6).

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 names the class Record, which can only be extended by a record class (8.10).

In a value class, it is a compile-time error if the ClassType names an identity class (8.1.1.5) other than the class Object.

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 important because its implementation is used to derive the implementation of the class being declared.

...

8.3 Field Declarations

8.3.1 Field Modifiers

8.3.1.2 final Fields

A field can be declared final (4.12.4). Both class and instance variables (static and non-static fields) may be declared final.

In a value class, every non-static field is implicitly final. It is permitted for the field declaration to redundantly specify the final modifier.

A blank final class variable must be definitely assigned by a static initializer of the class in which it is declared, or a compile-time error occurs (8.7, 16.8).

A blank final instance variable must be definitely assigned and moreover not definitely unassigned at the end of every constructor of the class in which it is declared, or a compile-time error occurs (8.8, 16.9).

In a value class, a blank final instance variable must be definitely assigned after the argument list of an explicit superclass constructor invocation (8.8.7.1).

8.3.2 Field Initialization

If a declarator in a field declaration has a variable initializer, then the declarator has the semantics of an assignment (15.26) to the declared variable.

If the declarator is for a class variable (that is, a static field) (8.3.1.1), then the following rules apply to its initializer: the initializer occurs in a static context (8.1.3); references to the class variable being declared or forward references to other class variables may also be restricted, according to the rules in 8.3.3. At run time, the initializer is evaluated and the assignment performed exactly once, when the class is initialized (12.4.2).

Note that static fields that are constant variables (4.12.4) are initialized before other static fields (12.4.2, step 6). This also applies in interfaces (9.3.1). When such fields are referenced by simple name, they will never be observed to have their default initial values (4.12.5).

If the declarator is for an instance variable (that is, a field that is not static) of a value class (8.1.1.5), then the initializer is an early instance variable initializer and occurs in an early construction context (8.8.7.1); the initializer may refer to any class variable of the class, even one whose declaration occurs to the right of the initializer. At run time, the initializer is evaluated and the assignment performed each time an instance of the class is created, at the start of the class's construction process (12.5).

If the declarator is for an instance variable (that is, a field that is not static) of an identity class, then the following rules apply to its initializer: the initializer is a late instance variable initializer. The initializer may freely refer to the current object using the keyword this or the keyword super, and may refer to any class variable of the class, even one whose declaration occurs to the right of the initializer; references to the instance variable being declared or forward references to other instance variables may be restricted, according to the rules in 8.3.3. At run time, the initializer is evaluated and the assignment performed each time an instance of the class is created, immediately after the superclass constructor invocation (12.5).

References from variable initializers to fields that may not yet be initialized are restricted, as specified in 8.3.3 and 16.

Exception checking for a variable initializer in a field declaration is specified in 11.2.3.

Variable initializers are also used in local variable declaration statements (14.4), where the initializer is evaluated and the assignment performed each time the local variable declaration statement is executed.

Example 8.3.2-1. Field Initialization

class Point {
    int x = 1, y = 5;
}
class Test {
    public static void main(String[] args) {
        Point p = new Point();
        System.out.println(p.x + ", " + p.y);
    }
}

This program produces the output:

1, 5

because the assignments to x and y occur whenever a new Point is created.

Example 8.3.2-2. Forward Reference to a Class Variable

class Test {
    float f = j;
    static int j = 1;
}

This program compiles without error; it initializes j to 1 when class Test is initialized, and initializes f to the current value of j every time an instance of class Test is created.

8.4 Method Declarations

8.4.3 Method Modifiers

8.4.3.6 synchronized Methods

A synchronized method acquires a monitor (17.1) before it executes.

For a class (static) method, the monitor associated with the Class object for the method's class is used.

For an instance method, the monitor associated with this (the object for which the method was invoked) is used.

It is a compile-time error for a value class to declare a synchronized instance method.

...

8.8 Constructor Declarations

8.8.7 Constructor Body

A constructor body is a block of code that is executed as part of the process of creating a new instance of a class (12.5). A constructor body may contain an explicit invocation of another constructor of the same class or of the direct superclass (8.8.7.1).

ConstructorBody:
{ [BlockStatements] }
{ [BlockStatements] ExplicitConstructorInvocation [BlockStatements] }

If a constructor body contains an explicit constructor invocation, the BlockStatements preceding the explicit constructor invocation are called the prologue of the constructor body. The prologue of a constructor body may be empty. The BlockStatements in a constructor with no explicit constructor invocation and the BlockStatements following an explicit constructor invocation in a constructor body are called the epilogue. The epilogue of a constructor body may also be empty.

If the body of a constructor in a value class (8.1.1.5) does not contain an explicit constructor invocation, then the BlockStatements constitute the prologue, and the epilogue is empty. The constructor body implicitly ends with a superclass constructor invocation "super();", an invocation of the constructor of the direct superclass that takes no arguments.

If a constructor body the body of a constructor in an identity class does not contain an explicit constructor invocation, then the prologue is empty and the BlockStatements constitute the epilogue. and If the constructor being declared is not part of the primordial class Object, then the constructor body implicitly begins with a superclass constructor invocation "super();", an invocation of the constructor of the direct superclass that takes no arguments.

Except for the possibility of explicit or implicit constructor invocations, and the prohibitions on return statements (14.17), the body of a constructor is like the body of a method (8.4.7).

Note that a constructor body contains at most one explicit constructor invocation. The grammar makes it impossible, for example, to place explicit constructor invocations in different branches of an if statement.

Example 8.8.7-1. Constructor Bodies

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}
class ColoredPoint extends Point {
    static final int WHITE = 0, BLACK = 1;
    int color;
    ColoredPoint(int x, int y) {
        this(x, y, WHITE);
    }
    ColoredPoint(int x, int y, int color) {
        super(x, y);
        this.color = color;
    }
}

Here, the first constructor of ColoredPoint invokes the second, providing an additional argument; the second constructor of ColoredPoint invokes the constructor of its superclass Point, passing along the coordinates.

8.8.7.1 Explicit Constructor Invocations
ExplicitConstructorInvocation:
[TypeArguments] this ( [ArgumentList] ) ;
[TypeArguments] super ( [ArgumentList] ) ;
ExpressionName . [TypeArguments] super ( [ArgumentList] ) ;
Primary . [TypeArguments] super ( [ArgumentList] ) ;

The following productions from 4.5.1 and 15.12 are shown here for convenience:

TypeArguments:
< TypeArgumentList >
ArgumentList:
Expression {, Expression}

Explicit constructor invocations are divided into two kinds:

It is a compile-time error for a constructor to directly or indirectly invoke itself through a series of one or more alternate constructor invocations.

If TypeArguments is present to the left of this or super, then it is a compile-time error if any of the type arguments are wildcards (4.5.1).

The rules for a superclass constructor invocation, where C is the class being instantiated, and S is the direct superclass of C, are as follows:

If a superclass constructor invocation is unqualified, then:

If a superclass constructor invocation is qualified, then:

The exception types that an explicit constructor invocation can throw are specified in 11.2.2.

An expression occurs in the early construction context of a class C if it is contained in either one of the prologue of a constructor body of C, or it is nested in the explicit constructor invocation of a constructor body of C, or an early instance field initializer (8.3.2) of C.

Expressions that occur in the early construction context of a class C are restricted in the following ways:

...

8.9 Enum Classes

An enum declaration specifies a new enum class, a restricted kind of class that defines a small set of named class instances.

EnumDeclaration:
{ClassModifier} enum TypeIdentifier [ClassImplements] EnumBody

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

The TypeIdentifier in an enum declaration specifies the name of the enum class.

It is a compile-time error if an enum declaration has the modifier abstract, final, sealed, or non-sealed, or value.

An enum class is either implicitly final or implicitly sealed, as follows:

A nested enum class is implicitly static. That is, every member enum class and local enum class is static. It is permitted for the declaration of a member enum class to redundantly specify the static modifier, but it is not permitted for the declaration of a local enum class (14.3).

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

The direct superclass type of an enum class E is Enum<E> (8.1.4).

An enum declaration does not have an extends clause, so it is not possible to explicitly declare a direct superclass type, even Enum<E>.

An enum class has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum class (15.9.1).

In addition to the compile-time error, three further mechanisms ensure that no instances of an enum class exist beyond those defined by its enum constants:

8.10 Record Classes

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

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

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

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

It is a compile-time error if a record declaration has the modifier abstract, sealed, or non-sealed.

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

A nested record class is implicitly static. That is, every member record class and local record class is static. It is permitted for the declaration of a member record class to redundantly specify the static modifier, but it is not permitted for the declaration of a local record class (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).

A record class may be a value class or an identity class (8.1.1.5).

A record class is often a good candidate to be a value class, because its instance fields are always final and its implicitly declared equals method makes no use of identity (8.10.3).

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

A record declaration does not have an extends clause, so it is not possible to explicitly declare a direct superclass type, even 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).

Chapter 12: Execution

12.5 Creation of New Class Instances

A new class instance is explicitly created when evaluation of a class instance creation expression (15.9) causes a class to be instantiated.

A new class instance may be implicitly created in the following situations:

Each of these situations identifies a particular constructor (8.8) to be called with specified arguments (possibly none) as part of the class instance creation process.

Whenever a new class instance is created, memory space is allocated for it with room for all the instance variables declared in the class and all the instance variables declared in each superclass of the class, including all the instance variables that may be hidden (8.3).

If there is not sufficient space available to allocate memory for the object, then creation of the class instance completes abruptly with an OutOfMemoryError. Otherwise, all the instance variables in the new object, including those declared in superclasses, are initialized to their default values (4.12.5).

Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:

  1. Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.

  2. If this constructor does not contain an explicit constructor invocation (8.8.7.1) then continue from step 5. If this constructor is declared in a value class (8.1.1.5) and does not contain an alternate constructor invocation (8.8.7.1), execute the early instance variable initializers for this class (8.3.2), assigning the values of the initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code of the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception, otherwise continue with the next step.

  3. Execute the BlockStatements, if any, of the prologue of the constructor body. If execution of any statement completes abruptly, then execution of the constructor completes abruptly for the same reason, otherwise continue with the next step.

  4. If this constructor contains an explicit constructor invocation, The explicit constructor the invocation is either an invocation of another constructor in the same class (using this) or an invocation of a superclass constructor (using super). Evaluate the arguments of the constructor invocation and process the constructor invocation recursively using these same seven steps. If the constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue from step 7 if the invocation is of another constructor in the same class, and continue from step 6 if the invocation is of a superclass constructor.

  5. If this constructor contains no explicit constructor invocation and is for a class other than Object, then this constructor contains an implicit invocation of a superclass constructor with no arguments. In this case, process the implicit constructor invocation recursively using these same seven steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason, otherwise continue with the next step.

  6. Execute the instance initializers and late instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise continue with the next step.

    In a value class, the instance variable initializers are all early initializers and were already executed in step 2.

  7. Execute the BlockStatements, if any, of the epilogue of this constructor. If execution of any statement completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.

Unlike C++, the Java programming language does not specify altered rules for method dispatch during the creation of a new class instance. If methods are invoked that are overridden in subclasses in the object being initialized, then these overriding methods are used, even before the new object is completely initialized. Classes can avoid unwanted exposure of uninitialized state by assigning to their fields in the prologue of the constructor body.

Example 12.5-1. Evaluation of Instance Creation

class Point {
    int x, y;
    Point() { x = 1; y = 1; }
}
class ColoredPoint extends Point {
    int color = 0xFF00FF;
}
class Test {
    public static void main(String[] args) {
        ColoredPoint cp = new ColoredPoint();
        System.out.println(cp.color);
    }
}

Here, a new instance of ColoredPoint is created. First, space is allocated for the new ColoredPoint, to hold the fields x, y, and color. All these fields are then initialized to their default values (in this case, 0 for each field). Next, the ColoredPoint constructor with no arguments is first invoked. Since ColoredPoint declares no constructors, a default constructor of the following form is implicitly declared:

ColoredPoint() { super(); }

This constructor then invokes the Point constructor with no arguments. The Point constructor does not begin with an invocation of a constructor, so the Java compiler provides an implicit invocation of its superclass constructor of no arguments, as though it had been written:

Point() { super(); x = 1; y = 1; }

Therefore, the constructor for Object which takes no arguments is invoked.

The class Object has no superclass, so the recursion terminates here. Next, any instance initializers and instance variable initializers of Object are invoked. Next, the body of the constructor of Object that takes no arguments is executed. No such constructor is declared in Object, so the Java compiler supplies a default one, which in this special case is:

Object() { }

This constructor executes without effect and returns.

Next, all initializers for the instance variables of class Point are executed. As it happens, the declarations of x and y do not provide any initialization expressions, so no action is required for this step of the example. Then the body of the Point constructor is executed, setting x to 1 and y to 1.

Next, the initializers for the instance variables of class ColoredPoint are executed. This step assigns the value 0xFF00FF to color. Finally, the epilogue of the ColoredPoint constructor is executed (the part after the invocation of super); there happen to be no statements in the epilogue, so no further action is required and initialization is complete.

Example 12.5-2. Dynamic Dispatch During Instance Creation

class Super {
    Super() { printThree(); }
    void printThree() { System.out.println("three"); }
}
class Test extends Super {
    int three = (int)Math.PI;  // That is, 3
    void printThree() { System.out.println(three); }

    public static void main(String[] args) {
        Test t = new Test();
        t.printThree();
    }
}

This program produces the output:

0
3

This shows that the invocation of printThree in the constructor for class Super does not invoke the definition of printThree in class Super, but rather invokes the overriding definition of printThree in class Test. This method therefore runs before the field initializers of Test have been executed, which is why the first value output is 0, the default value to which the field three of Test is initialized. The later invocation of printThree in method main invokes the same definition of printThree, but by that point the initializer for instance variable three has been executed, and so the value 3 is printed.

Example 12.5-3. Initialization of Fields in the Prologue

class Super {
    Super() { printThree(); }
    void printThree() { System.out.println("three"); }
}
class Test extends Super {
    int three;

    public Test() {
        three = (int)Math.PI;  // That is, 3
        super();
    }

    void printThree() { System.out.println(three); }

    public static void main(String[] args) {
        Test t = new Test();
        t.printThree();
    }
}

This alternative to Example 12.5-2 produces the output:

3
3

Because the field three is initialized in the prologue of the Test class's constructor, its assignment occurs in Step 3 of the object initialization procedure, before evaluation of the Super class's constructor body in Step 4. When three is initialized in this way, it is impossible to observe it with the default value 0.

Chapter 13: Binary Compatibility

13.4 Evolution of Classes

This section describes the effects of changes to the declaration of a class and its members and constructors on pre-existing binaries.

13.4.1 abstract and value Classes

If a class that was not declared abstract is changed to be declared abstract, then pre-existing binaries that attempt to create new instances of that class will throw either an InstantiationError at link time, or (if a reflective method is used) an InstantiationException at run time; such a change is therefore not recommended for widely distributed classes.

Changing a an identity class that is declared abstract to no longer be declared abstract does not break compatibility with pre-existing binaries.

Removing the abstract modifier from a value class declaration has the side-effect of making the class final, with binary compatibility risks outlined in 13.4.2.3.

Modifying an abstract or final identity class to be a value class does not break compatibility with pre-existing binaries.

Adding the value modifier to a non-abstract, non-final class declaration has the side-effect of making the class final, with binary compatibility risks outlined in 13.4.2.3.

Removing the value modifier from a non-abstract value class does not break compatibility with pre-existing binaries.

Removing the value modifier from an abstract value class causes an IncompatibleClassChangeError to be thrown whenever a binary of a pre-existing value subclass of the class is loaded, because value classes cannot extend identity classes (8.1.4); such a change is not recommended for widely distributed classes.

Aside from binary compatibility risks, changing an identity class to be a value class, or vice versa, will change instances' behavior with respect to indentity-sensitive operations like == (15.21.3) and synchronized (14.19), and may affect the timing of initializer and constructor logic (12.5).

Chapter 14: Blocks, Statements, and Patterns

14.19 The synchronized Statement

A synchronized statement acquires a mutual-exclusion lock (17.1) on behalf of the executing thread, executes a block, then releases the lock. While the executing thread owns the lock, no other thread may acquire the lock.

SynchronizedStatement:
synchronized ( Expression ) Block

The type of Expression must be a reference type, and must not be a final value class type, or a type variable or intersection type bounded by a final value class type, or a compile-time error occurs.

A synchronized statement is executed by first evaluating the Expression. Then:

The locks acquired by synchronized statements are the same as the locks that are acquired implicitly by synchronized methods (8.4.3.6). A single thread may acquire a lock more than once.

Acquiring the lock associated with an identity object does not in itself prevent other threads from accessing fields of the object or invoking un-synchronized methods on the object. Other threads can also use synchronized methods or the synchronized statement in a conventional manner to achieve mutual exclusion.

Example 14.19-1. The synchronized Statement

class Test {
    public static void main(String[] args) {
        Test t = new Test();
        synchronized(t) {
            synchronized(t) {
                System.out.println("made it!");
            }
        }
    }
}

This program produces the output:

made it!

Note that this program would deadlock if a single thread were not permitted to lock a monitor more than once.

Chapter 15: Expressions

15.9 Class Instance Creation Expressions

15.9.4 Run-Time Evaluation of Class Instance Creation Expressions

At run time, evaluation of a class instance creation expression is as follows.

First, if the class instance creation expression is a qualified class instance creation expression, the qualifying primary expression is evaluated. If the qualifying expression evaluates to null, a NullPointerException is raised, and the class instance creation expression completes abruptly. If the qualifying expression completes abruptly, the class instance creation expression completes abruptly for the same reason.

Next, space is allocated for the new class instance. If there is insufficient space to allocate the object, evaluation of the class instance creation expression completes abruptly by throwing an OutOfMemoryError.

The new object contains new instances of all the fields declared in the specified class and all its superclasses. As each new field instance is created, it is initialized to its default value (4.12.5).

Next, the actual arguments to the constructor are evaluated, left-to-right. If any of the argument evaluations completes abruptly, any argument expressions to its right are not evaluated, and the class instance creation expression completes abruptly for the same reason.

Next, the selected constructor of the specified class is invoked. This results in invoking at least one constructor for each superclass of the class. This process can be directed by explicit constructor invocation statements (8.8.7.1) and is specified in detail in 12.5.

The value of a class instance creation expression is a reference to the newly created object of the specified class. Every time If the specified class is an identity class, then every time the expression is evaluated, a fresh object is created the object that is created has a unique identity.

Example 15.9.4-1. Evaluation Order and Out-Of-Memory Detection

If evaluation of a class instance creation expression finds there is insufficient memory to perform the creation operation, then an OutOfMemoryError is thrown. This check occurs before any argument expressions are evaluated.

So, for example, the test program:

class List {
    int value;
    List next;
    static List head = new List(0);
    List(int n) { value = n; next = head; head = this; }
}
class Test {
    public static void main(String[] args) {
        int id = 0, oldid = 0;
        try {
            for (;;) {
                ++id;
                new List(oldid = id);
            }
        } catch (Error e) {
            List.head = null;
            System.out.println(e.getClass() + ", " + (oldid==id));
        }
    }
}

prints:

class java.lang.OutOfMemoryError, false

because the out-of-memory condition is detected before the argument expression oldid = id is evaluated.

Compare this to the treatment of array creation expressions, for which the out-of-memory condition is detected after evaluation of the dimension expressions (15.10.2).

15.9.5 Anonymous Class Declarations

An anonymous class is implicitly declared by a class instance creation expression or by an enum constant that ends with a class body (8.9.1).

An anonymous class is never abstract (8.1.1.1).

An anonymous class is never sealed (8.1.1.2), and thus has no permitted direct subclasses (8.1.6).

An anonymous class declared by a class instance creation expression is never final (8.1.1.2).

An anonymous class declared by an enum constant is always final.

An anonymous class being non-final is relevant in casting, in particular the narrowing reference conversion allowed for the cast operator (5.5). On the other hand, it is not relevant to subclassing, because it is impossible to declare a subclass of an anonymous class (an anonymous class cannot be named by an extends clause) despite the anonymous class being non-final.

An anonymous class is always an identity class (8.1.1.5) and an inner class (8.1.3).

Like a local class or interface (14.3), an anonymous class is not a member of any package, class, or interface (7.1, 8.5).

The direct superclass type or direct superinterface type of an anonymous class declared by a class instance creation expression is given by the expression (15.9.1), with type arguments inferred as necessary while choosing a constructor (15.9.3). If a direct superinterface type is given, the direct superclass type is Object.

The direct superclass type of an anonymous class declared by an enum constant is the type of the declaring enum class.

The ClassBody of the class instance creation expression or enum constant declares fields (8.3), methods (8.4), member classes (8.5), member interfaces (9.1.1.3), instance initializers (8.6), and static initializers (8.7) of the anonymous class. The constructor of an anonymous class is always implicit (15.9.5.1).

If a class instance creation expression with a ClassBody uses a diamond (<>) for the type arguments of the class to be instantiated, then for all non-private methods declared in the ClassBody, it is as if the method declaration is annotated with @Override (9.6.4.4).

When <> is used, the inferred type arguments may not be as anticipated by the programmer. Consequently, the supertype of the anonymous class may not be as anticipated, and methods declared in the anonymous class may not override supertype methods as intended. Treating such methods as if annotated with @Override (if they are not explicitly annotated with @Override) helps avoid silently incorrect programs.

15.21 Equality Operators

15.21.3 Reference Object Equality Operators == and !=

If the operands of an equality operator are both of either reference type or the null type, then the operation is object equality.

It is a compile-time error if it is impossible to convert the type of either operand to the type of the other by a casting conversion (5.5). The run-time values of the two operands would necessarily be unequal (ignoring the case where both values are null).

At run time, the result of == is true if the operand values are both null or both refer to the same object or array (4.3.1); otherwise, the result is false.

The result of != is false if the operand values are both null or both refer to the same object or array; otherwise, the result is true.

While == may be used to compare references of type String, such an equality test determines whether or not the two operands refer to the same String object. The result is false if the operands are distinct String objects, even if they contain the same sequence of characters (3.10.5, 3.10.6). The contents of two strings s and t can be tested for equality by the method invocation s.equals(t).

As outlined in 4.3.1, an identity object is only ever the same as itself, while a value object is the same as all instances of the same class with the same field values.

A common mistake is to use the == operator to test for the same object, when what is usually wanted is more abstract, testing whether two objects model the same value or entity. The equals method serves this purpose, allowing classes to declare an appropriate domain-specific notion of equality.

For example, two distinct String objects, s and t, may contain the same sequence of characters. A comparison of these objects using s == t will evaluate to false, while in most cases the program ought to use s.equals(t) to compare the character sequences that the objects represent.

Chapter 16: Definite Assignment

16.9 Definite Assignment, Constructors, and Instance Initializers

Let C be a class declared within the scope of V. Then:

Let C be a class, and let V be a blank final non-static member field of C, declared in C. Then:

Let C be a class, and let V be a blank final non-static member field of C, declared in a superclass of C. Then:

Let C be a class, and let V be a blank final static member field of C. Then:

Let C be a class, and let V be a local variable declared by a statement S contained by a constructor or instance variable initializer of C. Then:

The following rules hold within the constructors (8.8.7) of class C:

Chapter 17: Threads and Locks

17.1 Synchronization

The Java programming language provides multiple mechanisms for communicating between threads. The most basic of these methods is synchronization, which is implemented using monitors. Each identity object in Java is associated with a monitor, which a thread can lock or unlock. Only one thread at a time may hold a lock on a monitor. Any other threads attempting to lock that monitor are blocked until they can obtain a lock on that monitor. A thread t may lock a particular monitor multiple times; each unlock reverses the effect of one lock operation.

The synchronized statement (14.19) computes a reference to an object; it then attempts to perform a lock action on that object's monitor and does not proceed further until the lock action has successfully completed. After the lock action has been performed, the body of the synchronized statement is executed. If execution of the body is ever completed, either normally or abruptly, an unlock action is automatically performed on that same monitor.

A synchronized method (8.4.3.6) automatically performs a lock action when it is invoked; its body is not executed until the lock action has successfully completed. If the method is an instance method, it locks the monitor associated with the instance for which it was invoked (that is, the object that will be known as this during execution of the body of the method). If the method is static, it locks the monitor associated with the Class object that represents the class in which the method is defined. If execution of the method's body is ever completed, either normally or abruptly, an unlock action is automatically performed on that same monitor.

The Java programming language neither prevents nor requires detection of deadlock conditions. Programs where threads hold (directly or indirectly) locks on multiple objects should use conventional techniques for deadlock avoidance, creating higher-level locking primitives that do not deadlock, if necessary.

Other mechanisms, such as reads and writes of volatile variables and the use of classes in the java.util.concurrent package, provide alternative ways of synchronization.

17.2 Wait Sets and Notification

Every identity object, in addition to having an associated monitor, has an associated wait set. A wait set is a set of threads.

When an identity object is first created, its wait set is empty. Elementary actions that add threads to and remove threads from wait sets are atomic. Wait sets are manipulated solely through the methods Object.wait, Object.notify, and Object.notifyAll.

Wait set manipulations can also be affected by the interruption status of a thread, and by the Thread class's methods dealing with interruption. Additionally, the Thread class's methods for sleeping and joining other threads have properties derived from those of wait and notification actions.