This document describes changes to the Java Language Specification to support value objects, a preview feature introduced by a draft JEP.
A companion documents describes JVM changes.
Key changes include:
Value objects as a new kind of object, with special behaviors for instantiation,
==
, and synchronizationNew
identity
andvalue
context-sensitive keywords for use as class and interface modifiersSpecial restrictions applied to
value
class declarations
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:
August 2022: fixed rules about
synchronized
methods (8.1.1.5, 8.4.3.6) and binary compatibility ofidentity
/value
classes (13.4.1)May 2022: clarified that
identity
constrains the subclasses of a concrete class (8.1.1.5); added rules aboutabstract
inner classes (8.1.3) andabstract
classes with instance initializers (8.6) or constructors (8.8)April 2022: initial draft
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
,identity
,var
, oryield
- 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
,identity
,var
, oryield
.
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 namedyield
must be qualified, thus distinguishing the invocation from ayield
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
and identity
keywords (along with sealed
) without disallowing these as class names.
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
and identity
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 16 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
module permits sealed var
non-sealed provides to with
open record transitive yield
value identity
The keywords
const
andgoto
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) is reserved for possible future use in parameter declarations.
true
andfalse
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:
The sequence is recognized as a terminal specified in a suitable context of the syntactic grammar (2.3), as follows:
For
module
andopen
, when recognized as a terminal in a ModuleDeclaration (7.7).For
exports
,opens
,provides
,requires
,to
,uses
, andwith
, 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 termtransitive
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, becausevar
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, becauseyield
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
, andsealed
,value
, andidentity
, when recognized as aterminalClassModifier or InterfaceModifier in a NormalClassDeclaration (8.1), EnumDeclaration (8.9), RecordDeclaration (8.10),or aNormalInterfaceDeclaration (9.1), or AnnotationInterfaceDeclaration (9.6).
Some combinations of modifiers and declarations don't make sense, like a
value enum
. However, these are semantic restrictions that should not affect parsing.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 identifierpublicstatic
, rather than as the reserved keywordspublic
andstatic
. 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 identifiervarfilename
, but in a local variable declaration, the first three input characters are tentatively recognized as the contextual keywordvar
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 identifierfilename
. (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 keywordvar
on the grounds that the immediately following input characterf
is a JavaLetterOrDigit. The sequencev a r f i l e n a m e
is therefore tokenized as the identifiervarfilename
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 identifiernon
, the operator-
, and the identifiersealedclass
- but in a normal class declaration, where the first condition holds, the first ten input characters are tentatively recognized as the contextual keywordnon-sealed
. To avoid translating the sequence to two keyword tokens (non-sealed
andclass
) rather than three non-keyword tokens, and to avoid rewarding the programmer for omitting white space beforeclass
, the second condition prevents recognition of the contextual keyword. The sequencen 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 fields of a floating-point primitive type, the field values are considered the same if the results of the appropriate Float.floatToIntBits
or Double.doubleToLongBits
method are the same. For fields of a reference type, the field values are considered the same if they are both null
, or if they are references to the same object (possibly applying this definition recursively).
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) are pointers to objects, and a special null reference, which refers to no object.
The operators on references to objects are:
Field access, using either a qualified name (6.6) or a field access expression (15.11)
Method invocation (15.12)
The string concatenation operator
+
(15.18.1), which, when given aString
operand and a reference, will convert the reference to aString
by invoking thetoString
method of the referenced object (using"null"
if either the reference or the result oftoString
is a null reference), and then will produce a newly createdString
that is the concatenation of the two stringsThe
instanceof
operator (15.20.2)The
referenceobject equality operators==
and!=
(15.21.3)The conditional operator
? :
(15.25).
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 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
identity
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
.
It is a compile-time error if a class declaration has both of the modifiers identity
and value
.
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.5 identity
and value
Classes
The identity
modifier specifies that all instances of a class (15.9.4) and its subclasses (8.1.4) are identity objects (4.3.1). The value
modifier specifies that all instances of a class and its subclasses are value objects.
A class with neither the modifier identity
nor value
is implicitly an identity
class if it has any of the following properties:
The class is neither
abstract
not the classObject
The class is
abstract
and declares an instance field (8.3.1.1) or has an enclosing instance (8.1.3)The class is
abstract
and declares asynchronized
instance method (8.4.3.6)The class is
abstract
and declares a nontrivial constructor (8.8) or an instance initializer (8.6). A constructor is considered "nontrivial" for the purpose of this rule if its level of access is more restrictive than the class (8.8.3), it declares formal parameters (8.8.1) or type parameters (8.8.4), it has athrows
clause (8.8.5), or its body contains something other than an explicit constructor invocation of the formsuper();
(8.8.7).
Thus, every class—with the special exception of
Object
—is either anidentity
class, avalue
class, or anabstract
class that can support both kinds of subclasses.
To avoid confusion, an
abstract
class that declares no fields but that is implicitly anidentity
class should, as a matter of style, be explicitly declared anidentity
class.
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.
The identity
and value
modifiers limit the set of classes that can extend a non-final
class (8.1.4).
Special restrictions apply to the field declarations (8.3.1), method declarations (8.4.3.6), and constructors (8.8.7) of a class that is not an identity
class.
For a discussion of how we expect typical abstract
classes to be impacted by these rules, see this email.
There is no way for an abstract
class to assert that it is intended to be compatible with both value
and identity
subclasses. Should there be? (Compare the @FunctionalInterface
annotation.)
8.1.3 Inner Classes and Enclosing Instances
...
An inner class C is a direct inner class of a class or interface O if O is the immediately enclosing class or interface declaration of C and the declaration of C does not occur in a static context.
If an inner class is a local class or an anonymous class, it may be declared in a static context, and in that case is not considered an inner class of any enclosing class or interface.
A class C is an inner class of class or interface O if it is either a direct inner class of O or an inner class of an inner class of O.
It is unusual, but possible, for the immediately enclosing class or interface declaration of an inner class to be an interface. This only occurs if the class is a local or anonymous class declared in a
default
orstatic
method body (9.4).
A class or interface O is the zeroth lexically enclosing class or interface declaration of itself.
A class O is the n'th lexically enclosing class declaration of a class C if it is the immediately enclosing class declaration of the n-1'th lexically enclosing class declaration of C.
An instance i of a direct inner class C of a class or interface O is associated with an instance of O, known as the immediately enclosing instance of i. The immediately enclosing instance of an object, if any, is determined when the object is created (15.9.2).
An object o is the zeroth lexically enclosing instance of itself.
An object o is the n'th lexically enclosing instance of an instance i if it is the immediately enclosing instance of the n-1'th lexically enclosing instance of i.
An instance of an inner local class or an anonymous class whose declaration occurs in a static context has no immediately enclosing instance. Also, an instance of a static
nested class (8.1.1.4) has no immediately enclosing instance.
It is a compile-time error if an inner class has an immediately enclosing instance but is declared an abstract
value
class (8.1.1.1, 8.1.1.5).
If an abstract class is declared with neither the
value
nor theidentity
modifier, but it is an inner class and has an immediately enclosing instance, it is implicitly anidentity
class, per 8.1.1.5.
...
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).
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 class
Object
has no direct superclass type.For a class other than
Object
with a normal class declaration, the direct superclass type isObject
.For an enum class E, the direct superclass type is
Enum<
E>
.For an anonymous class, the direct superclass type is defined in 15.9.5.
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.
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 is the direct superclass of C.
Where a class B is the direct superclass of C, A is a superclass of B, applying this definition recursively.
A class is said to be a direct subclass of its direct superclass, and a subclass of each of its superclasses.
It is a compile-time error if an identity
class (8.1.1.5) has a superclass that is a value
class, or if a value
class has a superclass that is an identity
class.
...
8.1.5 Superinterfaces
The optional implements
clause in a class declaration specifies the direct superinterface types of the class being declared.
- ClassImplements:
implements
InterfaceTypeList- InterfaceTypeList:
- InterfaceType {
,
InterfaceType}
Each InterfaceType must name an accessible interface (6.6), or a compile-time error occurs.
It is a compile-time error if any InterfaceType names a interface that is sealed
(9.1.1.4) and the class being declared is not a permitted direct subclass of the named interface (9.1.4).
If an InterfaceType 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.
It is a compile-time error if the same interface is named by a direct superinterface type more than once in a single implements
clause. This is true even if the interface is named in different ways.
Example 8.1.5-1. Illegal Superinterfaces
class Redundant implements java.lang.Cloneable, Cloneable {
int x;
}
This program results in a compile-time error because the names java.lang.Cloneable
and Cloneable
refer to the same interface.
A class whose declaration lacks an implements
clause has no direct superinterface types, with one exception: an anonymous class may have a superinterface type (15.9.5).
An interface is a direct superinterface of a class if the interface is named by one of the direct superinterface types of the class.
An interface I is a superinterface of class C if any of the following is true:
I is a direct superinterface of C.
C has some direct superinterface J for which I is a superinterface, using the definition of "superinterface of an interface" given in 9.1.3.
I is a superinterface of the direct superclass of C.
A class can have a superinterface in more than one way.
A class is said to directly implement its direct superinterfaces, and to implement all of its superinterfaces.
A class is said to be a direct subclass of its direct superinterfaces, and a subclass of all of its superinterfaces.
It is a compile-time error if an identity
class (8.1.1.5) has a superinterface that is a value
interface, or if a value
class has a superinterface that is an identity
interface, or if any class has both an identity
superclass or superinterface and a value
superclass or superinterface.
A class may not declare a direct superclass type and a direct superinterface type, or two direct superinterface types, which are, or which have supertypes (4.10.2) which are, different parameterizations of the same generic interface (9.1.2), or a parameterization of a generic interface and a raw type naming that same generic interface. In the case of such a conflict, a compile-time error occurs.
This requirement was introduced in order to support translation by type erasure (4.6).
...
8.3 Field Declarations
8.3.1 Field Modifiers
8.3.1.1 static
Fields
If a field is declared static
, there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created. A static
field, sometimes called a class variable, is incarnated when the class is initialized (12.4).
A field that is not declared static
is called an instance variable, and sometimes called a non-static
field. Whenever a new instance of a class is created (12.5), a new variable associated with that instance is created for every instance variable declared in that class or any of its superclasses.
It is a compile-time error for an abstract
value
class to declare a non-static
field.
If an abstract class is declared with neither the
value
nor theidentity
modifier, but it declares a non-static
field, it is implicitly anidentity
class, per 8.1.1.5.
The declaration of a class variable introduces a static context (8.1.3), which limits the use of constructs that refer to the current object. Notably, the keywords this
and super
are prohibited in a static context (15.8.3, 15.11.2), as are unqualified references to instance variables, instance methods, and type parameters of lexically enclosing declarations (6.5.5.1, 6.5.6.1, 15.12.3).
References to an instance variable from a static context or a nested class or interface are restricted, as specified in 6.5.6.1.
...
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).
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.
If an
abstract
class is declared with neither thevalue
nor theidentity
modifier, but it declares asynchronized
instance method, it is implicitly anidentity
class, per 8.1.1.5.
...
8.6 Instance Initializers
An instance initializer declared in a class is executed when an instance of the class is created (12.5, 15.9, 8.8.7.1).
- InstanceInitializer:
- Block
It is a compile-time error for an abstract
value
class to declare an instance initializer.
If an abstract class is declared with neither the
value
nor theidentity
modifier, but it declares an instance initializer, it is implicitly anidentity
class, per 8.1.1.5.
It is a compile-time error if an instance initializer cannot complete normally (14.22).
It is a compile-time error if a return
statement (14.17) appears anywhere within an instance initializer.
An instance initializer is permitted to refer to the current object using the keyword this
(15.8.3) or the keyword super
(15.11.2, 15.12), and to use any type variables in scope.
Restrictions on how an instance initializer may refer to instance variables, even when the instance variables are in scope, are specified in 8.3.3.
Exception checking for an instance initializer is specified in 11.2.3.
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, as stated 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.
It is a compile-time error for an abstract
value
class to declare a nontrivial constructor (8.1.1.5).
If an abstract class is declared with neither the
value
nor theidentity
modifier, but it declares a nontrivial constructor, it is implicitly anidentity
class, per 8.1.1.5.
It's not ideal to define a new term just for the purpose of this rule. But the list of things to check is long, and we don't want to repeat it. Perhaps it would be helpful to somehow overlap this definition with the discussion of default constructors in 8.8.9.
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.8.7 Constructor Body
The first statement of a constructor body may be an explicit invocation of another constructor of the same class or of the direct superclass (8.8.7.1).
- ConstructorBody:
{
[ExplicitConstructorInvocation] [BlockStatements]}
It is a compile-time error for the constructor of a non-abstract
value
class to invoke a constructor of the direct superclass.
It is a compile-time error for a constructor to directly or indirectly invoke itself through a series of one or more explicit constructor invocations involving this
.
If a constructor body does not begin with an explicit constructor invocation and the constructor being declared is not part of the primordial class Object
or a non-abstract
value
class, then the constructor body implicitly begins with a superclass constructor invocation "super();
", an invocation of the constructor of its direct superclass that takes no arguments.
Except for the possibility of explicit constructor invocations, and the prohibition on explicitly returning a value (14.17), the body of a constructor is like the body of a method (8.4.7).
A return
statement (14.17) may be used in the body of a constructor if it does not include an expression.
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.9 Default Constructor
If a class contains no constructor declarations, then a default constructor is implicitly declared. The form of the default constructor for a top level class, member class, or local class is as follows:
The default constructor has the same access modifier as the class, unless the class lacks an access modifier, in which case the default constructor has package access (6.6).
The default constructor has no formal parameters, except in a non-
private
inner member class, where the default constructor implicitly declares one formal parameter representing the immediately enclosing instance of the class (8.8.1, 15.9.2, 15.9.3).The default constructor has no
throws
clause.If the class being declared is the primordial class
Object
or a non-abstract
value
class, then the default constructor has an empty body. Otherwise, the default constructor simply invokes the superclass constructor with no arguments.
The form of the default constructor for an anonymous class is specified in 15.9.5.1.
It is a compile-time error if a default constructor is implicitly declared but the superclass does not have an accessible constructor that takes no arguments and has no throws
clause.
Example 8.8.9-1. Default Constructors
The declaration:
public class Point {
int x, y;
}
is equivalent to the declaration:
public class Point {
int x, y;
public Point() { super(); }
}
where the default constructor is public
because the class Point
is public
.
Example 8.8.9-2. Accessibility of Constructors v. Classes
The rule that the default constructor of a class has the same accessibility as the class itself is simple and intuitive. Note, however, that this does not imply that the constructor is accessible whenever the class is accessible. Consider:
package p1;
public class Outer {
protected class Inner {}
}
package p2;
class SonOfOuter extends p1.Outer {
void foo() {
new Inner(); // compile-time access error
}
}
The default constructor for Inner
is protected
. However, the constructor is protected
relative to Inner
, while Inner
is protected
relative to Outer
. So, Inner
is accessible in SonOfOuter
, since it is a subclass of Outer
. Inner
's constructor is not accessible in SonOfOuter
, because the class SonOfOuter
is not a subclass of Inner
! Hence, even though Inner
is accessible, its default constructor is not.
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:
An enum class is implicitly
final
if its declaration contains no enum constants that have a class body (8.9.1).An enum class E is implicitly
sealed
if its declaration contains at least one enum constant that has a class body. The permitted direct subclasses (8.1.6) of E are the anonymous classes implicitly declared by the enum constants that have a class body.
An enum class is implicitly an identity
class. It is permitted for the declaration of an enum class to redundantly specify the identity
modifier.
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, evenEnum<
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:
The
final
clone
method inEnum
ensures that enum constants can never be cloned.Reflective instantiation of enum classes is prohibited.
Special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization.
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 an identity
class or a value
class (8.1.1.5). It is a compile-time error if a record declaration has both of the modifiers identity
and value
. A record declaration with neither the identity
nor the value
modifier is implicitly an identity
class.
A record class is often a good candidate to be a
value
class, because its instance fields are alwaysfinal
and its implicitly declaredequals
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, evenRecord
.
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
...
It is a compile-time error for a record declaration to declare a record component with the name clone
, finalize
, getClass
, hashCode
, isValueObject
, notify
, notifyAll
, toString
, or wait
.
These are the names of the no-args
public
andprotected
methods inObject
. Disallowing them as the names of record components avoids confusion in a number of ways. First, every record class provides implementations ofhashCode
andtoString
that return representations of a record object as a whole; they cannot serve as accessor methods (8.10.3) for record components calledhashCode
ortoString
, and there would be no way to access such record components from outside the record class. Similarly, some record classes may provide implementations ofclone
and (regrettably)finalize
, so a record component calledclone
orfinalize
could not be accessed via an accessor method. Finally, thegetClass
,notify
,notifyAll
, andwait
methods inObject
arefinal
, so record components with the same names could not have accessor methods. (The accessor methods would have the same signatures as thefinal
methods, and would thus attempt, unsuccessfully, to override them.)
...
Chapter 9: Interfaces
9.1 Interface Declarations
9.1.1 Interface Modifiers
An interface declaration may include interface modifiers.
- InterfaceModifier:
- (one of)
- Annotation
public
protected
private
abstract
static
sealed
non-sealed
strictfp
identity
value
The rules concerning annotation modifiers for an interface declaration are specified in 9.7.4 and 9.7.5.
The access modifier public
(6.6) pertains only to top level interfaces (7.6) and member interfaces (8.5, 9.5), not to local interfaces (14.3).
The access modifiers protected
and private
pertain only to member interfaces.
The modifier static
pertains only to member interfaces and local interfaces.
It is a compile-time error if the same keyword appears more than once as a modifier for an interface declaration, or if a interface declaration has more than one of the access modifiers public
, protected
, and private
.
It is a compile-time error if an interface declaration has more than one of the modifiers sealed
and non-sealed
.
It is a compile-time error if an interface declaration has both of the modifiers identity
and value
.
If two or more (distinct) interface modifiers appear in an interface declaration, then it is customary, though not required, that they appear in the order consistent with that shown above in the production for InterfaceModifier.
9.1.1.5 identity
and value
Interfaces
The identity
modifier specifies that all instances of subclasses of an interface (8.1.5) are identity objects (4.3.1). The value
modifier specifies that all instances of subclasses of an interface are value objects.
By default, an interface is neither an identity
interface nor a value
interface, and may have instances of both kinds of objects.
The identity
and value
modifiers limit the set of classes and interfaces that can implement or extend an interface (8.1.5, 9.1.3).
9.1.3 Superinterfaces and Subinterfaces
If an extends
clause is provided, then the interface being declared extends each of the specified interface types and therefore inherits the member classes, member interfaces, instance methods, and static
fields of each of those interface types.
The specified interface types are the direct superinterface types of the interface being declared.
Any class that implements
the declared interface is also considered to implement all the interfaces that this interface extends
.
- InterfaceExtends:
extends
InterfaceTypeList
The following production from 8.1.5 is shown here for convenience:
- InterfaceTypeList:
- InterfaceType {
,
InterfaceType}
Each InterfaceType in the extends
clause of an interface declaration must name an accessible interface (6.6), or a compile-time error occurs.
It is a compile-time error if any InterfaceType names a interface that is sealed
(9.1.1.4) and the interface being declared is not a permitted direct subinterface of the named interface (9.1.4).
If an InterfaceType 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.
One interface is a direct superinterface of another interface if the first interface is named by one of the direct superinterface types of the second interface.
The superinterface relationship is the transitive closure of the direct superinterface relationship. An interface I is a superinterface of interface K if either of the following is true:
I is a direct superinterface of K.
Where J is a direct superinterface of K, I is a superinterface of J, applying this definition recursively.
An interface is said to be a direct subinterface of its direct superinterface, and a subinterface of each of its superinterfaces.
While every class is an extension of class Object
, there is no single interface of which all interfaces are extensions.
It is a compile-time error if an identity
interface (8.1.1.5) has a superinterface that is a value
interface, or if a value
interface has a superinterface that is an identity
interface, or if any interface has both an identity
superinterface and a value
superinterface.
An interface I directly depends on a class or interface A if A is mentioned in the extends
clause of I either as a superinterface or as a qualifier in the fully qualified form of a superinterface name.
An interface I depends on a class or interface A if any of the following is true:
I directly depends on A.
I directly depends on a class C that depends on A (8.1.5).
I directly depends on an interface J that depends on A, applying this definition recursively.
It is a compile-time error if an interface depends on itself.
If circularly declared interfaces are detected at run time, as interfaces are loaded, then a ClassCircularityError
is thrown (12.2.1).
9.6 Annotation Interfaces
An annotation interface declaration specifies an annotation interface, a specialized kind of interface. To distinguish an annotation interface declaration from a normal interface declaration, the keyword interface
is preceded by an at sign (@
).
- AnnotationInterfaceDeclaration:
- {InterfaceModifier}
@
interface
TypeIdentifier AnnotationInterfaceBody
Note that the at sign (
@
) and the keywordinterface
are distinct tokens. It is possible to separate them with whitespace, but this is discouraged as a matter of style.
Unless explicitly modified in this section and its subsections, all of the rules that apply to normal interface declarations (9.1) apply to annotation interface declarations.
For example, annotation interface declarations have the same rules for scope as normal interface declarations.
It is a compile-time error if an annotation interface declaration has the modifier sealed
, or non-sealed
, identity
, or value
(9.1.1.4).
An annotation interface declaration may specify a top level interface or a member interface, but not a local interface (14.3).
An annotation interface declaration is not permitted syntactically to appear within a block, by virtue of the LocalClassOrInterfaceDeclaration production in 14.3.
It is a compile-time error if an annotation interface declaration appears directly or indirectly in the body of a local class, local interface, or anonymous class declaration (14.3, 15.9.5).
This rule, together with the syntactic restriction on annotation interface declarations noted above, ensures that an annotation interface always has a canonical name (6.7). Having such a name is important because the purpose of an annotation interface is to be used by annotations in other compilation units. Since a local class or interface does not have a canonical name, an annotation interface declared anywhere within its syntactic body (if that were allowed) would not have a canonical name either.
...
9.8 Functional Interfaces
A functional interface is an interface that is not declared with one of the modifiers sealed
, identity
, or value
, and has just one abstract
method (aside from the methods of Object
), and thus represents a single function contract. This "single" method may take the form of multiple abstract
methods with override-equivalent signatures inherited from superinterfaces; in this case, the inherited methods logically represent a single method.
For an interface I that is not declared with one of the modifiers sealed
, identity
, or value
, let M be the set of abstract
methods that are members of I that do not have the same signature as any public
instance method of the class Object
(4.3.2). Then, I is a functional interface if there exists a method m in M for which both of the following are true:
The signature of m is a subsignature (8.4.2) of every method's signature in M.
m is return-type-substitutable (8.4.5) for every method in M.
In addition to the usual process of creating an interface instance by declaring and instantiating a class (15.9), instances of functional interfaces can be created with method reference expressions and lambda expressions (15.13, 15.27).
The definition of functional interface excludes methods in an interface that are also
public
methods inObject
. This is to allow functional treatment of an interface likejava.util.Comparator<T>
that declares multipleabstract
methods of which only one is really "new" -int compare(T,T)
. The other -boolean equals(Object)
- is an explicit declaration of anabstract
method that would otherwise be implicitly declared in the interface (9.2) and automatically implemented by every class thatimplements
the interface.
Note that if non-
public
methods ofObject
, such asclone()
, are explicitly declared in an interface aspublic
, they are not automatically implemented by every class thatimplements
the interface. The implementation inherited fromObject
isprotected
while the interface method ispublic
, so the only way to implement the interface would be for a class to override the non-public
Object
method with apublic
method.
...
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
, identity
, 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 class that is declared abstract
to no longer be declared abstract
does not break compatibility with may impact pre-existing binaries, depending on the identity
and value
modifiers:
If the class is a
value
class, removing theabstract
modifier has the side-effect of making the classfinal
, with binary compatibility risks outlined in 13.4.2.3.If the class is an
identity
class, removing theabstract
modifier is a compatible change for pre-existing binaries.If the class is neither an
identity
class nor avalue
class, removing theabstract
modifier has the side-effect of making the class anidentity
class, with binary compatibility risks outlined below.
If one of the identity
or value
modifiers is added to an abstract
class, a pre-existing binary that attempts to extend the class may fail to load. Specifically, if the subclass has an incompatible identity
or value
modifier, or implements an interface with an incompatible identity
or value
modifier, class loading will fail with an IncompatibleClassChangeError
.
Removing one of the identity
or value
modifiers from an abstract
class does not break compatibility with pre-existing binaries.
Many
abstract
classes are implicitlyidentity
classes (8.1.1.5). In that case, adding or removing the explicitidentity
modifier has no effect, but other changes to the class declaration may have the effect of removing the implicitidentity
modifier. For stability, developers are encouraged to explicitly declare such classes asidentity
classes.
Modifying a non-abstract
identity
class to be a value
class is a binary compatible change, as long as the class is already final
and all its constructors are private
. If the class is not final
, declaring it a value
class has the effect of declaring the class final
(13.4.2.3). If the class has a non-private
constructor, pre-existing binaries that attempt to invoke that constructor will throw an InstantiationError
at link time.
Modifying a non-abstract
value
class to be an identity
class is a binary compatible change, as long as all of the class's constructors are private
. If the class has a non-private
constructor, pre-existing binaries that attempt to invoke that constructor will behave as if the constructor had been removed (13.4.12).
13.4.7 Access to Members and Constructors
Changing the declared access of a member or constructor to permit less access may break compatibility with pre-existing binaries, causing a linkage error to be thrown when these binaries are resolved. Less access is permitted if the access modifier is changed from package access to private
access; from protected
access to package or private
access; or from public
access to protected
, package, or private
access. Changing a member or constructor to permit less access is therefore not recommended for widely distributed classes.
Changing the declared access of the constructor of an abstract
class that is not an identity
class to permit less access may also have the side-effect of making the class an identity
class (8.1.1.5), with binary compatibility risks outlined in 13.4.1.
Perhaps surprisingly, the binary format is defined so that changing a member or constructor to be more accessible does not cause a linkage error when a subclass (already) defines a method to have less access.
Example 13.4.7-1. Changing Accessibility
If the package points
defines the class Point
:
package points;
public class Point {
public int x, y;
protected void print() {
System.out.println("(" + x + "," + y + ")");
}
}
used by the program:
class Test extends points.Point {
public static void main(String[] args) {
Test t = new Test();
t.print();
}
protected void print() {
System.out.println("Test");
}
}
then these classes compile and Test
executes to produce the output:
Test
If the method print
in class Point
is changed to be public
, and then only the Point
class is recompiled, and then executed with the previously existing binary for Test
, then no linkage error occurs. This happens even though it is improper, at compile time, for a public
method to be overridden by a protected
method (as shown by the fact that the class Test
could not be recompiled using this new Point
class unless print
in Test
were changed to be public
.)
Allowing superclasses to change protected
methods to be public
without breaking binaries of pre-existing subclasses helps make binaries less fragile. The alternative, where such a change would cause a linkage error, would create additional binary incompatibilities.
13.4.8 Field Declarations
Widely distributed programs should not expose any fields to their clients. Apart from the binary compatibility issues discussed below, this is generally good software engineering practice. Adding a field to a class may break compatibility with pre-existing binaries that are not recompiled.
Assume a reference to a field f with qualifying class C. Assume further that f is in fact an instance (respectively static
) field declared in a superclass of C, S, and that the type of f is X.
If a new field of type X with the same name as f is added to a subclass of S that is a superclass of C or C itself, then a linkage error may occur. Such a linkage error will occur only if, in addition to the above, either one of the following is true:
The new field is less accessible than the old one.
The new field is a
static
(respectively instance) field.
In particular, no linkage error will occur in the case where a class could no longer be recompiled because a field access previously referenced a field of a superclass with an incompatible type. The previously compiled class with such a reference will continue to reference the field declared in a superclass.
Adding an instance field to an abstract
class that is not an identity
class also has the side-effect of making the class an identity
class (8.1.1.5), with binary compatibility risks outlined in 13.4.1.
Example 13.4.8-1. Adding A Field Declaration
class Hyper { String h = "hyper"; }
class Super extends Hyper { String s = "super"; }
class Test {
public static void main(String[] args) {
System.out.println(new Super().h);
}
}
This program produces the output:
hyper
Suppose a new version of class Super
is produced:
class Super extends Hyper {
String s = "super";
int h = 0;
}
Then, recompiling Hyper
and Super
, and executing the resulting new binaries with the old binary of Test
produces the output:
hyper
The field h
of Hyper
is output by the original binary of Test
. While this may seem surprising at first, it serves to reduce the number of incompatibilities that occur at run time. (In an ideal world, all source files that needed recompilation would be recompiled whenever any one of them changed, eliminating such surprises. But such a mass recompilation is often impractical or impossible, especially in the Internet. And, as was previously noted, such recompilation would sometimes require further changes to the source code.)
As another example, if the program:
class Hyper { String h = "Hyper"; }
class Super extends Hyper { }
class Test extends Super {
public static void main(String[] args) {
String s = new Test().h;
System.out.println(s);
}
}
is compiled and executed, it produces the output:
Hyper
Suppose that a new version of class Super
is then compiled:
class Super extends Hyper { char h = 'h'; }
If the resulting binary is used with the existing binaries for Hyper
and Test
, then the output is still:
Hyper
even though compiling the source for these binaries:
class Hyper { String h = "Hyper"; }
class Super extends Hyper { char h = 'h'; }
class Test extends Super {
public static void main(String[] args) {
String s = new Test().h;
System.out.println(s);
}
}
would result in a compile-time error, because the h
in the source code for main
would now be construed as referring to the char
field declared in Super
, and a char
value can't be assigned to a String
.
Deleting a field from a class will break compatibility with any pre-existing binaries that reference this field, and a NoSuchFieldError
will be thrown when such a reference from a pre-existing binary is linked. Only private
fields may be safely deleted from a widely distributed class.
For purposes of binary compatibility, adding or deleting a field f whose type involves type variables (4.4) or parameterized types (4.5) is equivalent to the addition (respectively, deletion) of a field of the same name whose type is the erasure (4.6) of the type of f.
13.4.10 static
Fields
If a field that is not declared private
was not declared static
and is changed to be declared static
, or vice versa, then a linkage error, specifically an IncompatibleClassChangeError
, will result if the field is used by a pre-existing binary which expected a field of the other kind. Such changes are not recommended in code that has been widely distributed.
Removing a static
modifier from a field of an abstract
class that is not an identity
class also has the side-effect of making the class an identity
class (8.1.1.5), with binary compatibility risks outlined in 13.4.1.
13.4.12 Method and Constructor Declarations
Adding a method or constructor to a class will not break compatibility with any pre-existing binaries, even in the case where a class could no longer be recompiled because an invocation previously referenced a method or constructor of a superclass with an incompatible type. The previously compiled class with such a reference will continue to reference the method or constructor declared in a superclass.
Assume a reference to a method m with qualifying class C. Assume further that m is in fact an instance (respectively static
) method declared in a superclass of C, S.
If a new method of type X with the same signature and return type as m is added to a subclass of S that is a superclass of C or C itself, then a linkage error may occur. Such a linkage error will occur only if, in addition to the above, either one of the following is true:
The new method is less accessible than the old one.
The new method is a
static
(respectively instance) method.
Adding a synchronized
method or a nontrivial constructor to an abstract
class that is not an identity
class also has the side-effect of making the class an identity
class (8.1.1.5), with binary compatibility risks outlined in 13.4.1.
Deleting a method or constructor from a class may break compatibility with any pre-existing binary that referenced this method or constructor; a NoSuchMethodError
may be thrown when such a reference from a pre-existing binary is linked. Such an error will occur only if no method with a matching signature and return type is declared in a superclass.
If the source code for a non-inner class contains no declared constructors, then a default constructor with no parameters is implicitly declared (8.8.9). Adding one or more constructor declarations to the source code of such a class will prevent this default constructor from being implicitly declared, effectively deleting a constructor, unless one of the new constructors also has no parameters, thus replacing the default constructor. The default constructor with no parameters is given the same access modifier as the class of its declaration, so any replacement should have as much or more access if compatibility with pre-existing binaries is to be preserved.
13.4.13 Method and Constructor Type Parameters
Adding or deleting a type parameter of a method or constructor does not, in itself, have any implications for binary compatibility.
If such a type parameter is used in the type of the method or constructor, that may have the normal implications of changing the aforementioned type.
Adding type parameters to the constructor of an abstract
class that is not an identity
class has the side-effect of making the class an identity
class (8.1.1.5), with binary compatibility risks outlined in 13.4.1.
Renaming a type parameter of a method or constructor has no effect with respect to pre-existing binaries.
Changing the first bound of a type parameter of a method or constructor may change the erasure (4.6) of any member that uses that type parameter in its own type, and this may affect binary compatibility. Specifically:
If the type parameter is used as the type of a field, the effect is as if the field were deleted and a field with the same name, whose type is the new erasure of the type variable, were added.
If the type parameter is used as the type of any formal parameter of a method, but not as the return type, the effect is as if that method were deleted, and replaced with a new method that is identical except for the types of the aforementioned formal parameters, which now have the new erasure of the type parameter as their type.
If the type parameter is used as a return type of a method, but not as the type of any formal parameter of the method, the effect is as if that method were deleted, and replaced with a new method that is identical except for the return type, which is now the new erasure of the type parameter.
If the type parameter is used as a return type of a method and as the type of one or more formal parameters of the method, the effect is as if that method were deleted, and replaced with a new method that is identical except for the return type, which is now the new erasure of the type parameter, and except for the types of the aforementioned formal parameters, which now have the new erasure of the type parameter as their types.
Changing any other bound has no effect on binary compatibility.
13.4.20 synchronized
Methods
Adding or deleting a synchronized
modifier of to a method of an identity
class does not break compatibility with pre-existing binaries.
Adding a synchronized
modifier to a method of an abstract
class that is not an identity
class has the side-effect of making the class an identity
class (8.1.1.5), with binary compatibility risks outlined in 13.4.1.
Deleting a synchronized
modifier of a method does not break compatibility with pre-existing binaries.
13.4.21 Method and Constructor Throws
Changes to the throws
clause of methods or constructors do not directly break compatibility with pre-existing binaries; these clauses are checked only at compile time.
However, adding a throws
clause to the constructor of an abstract
class that is not an identity
class has the side-effect of making the class an identity
class (8.1.1.5), with binary compatibility risks outlined in 13.4.1.
13.4.22 Method and Constructor Body
Changes to the body of a method or constructor do not directly break compatibility with pre-existing binaries.
However, changes to the body of the constructor of an abstract
class that is not an identity
class, other than adding or removing the super();
call, have the side-effect of making the class an identity
class (8.1.1.5), with binary compatibility risks outlined in 13.4.1.
The keyword final
on a method does not mean that the method can be safely inlined; it means only that the method cannot be overridden. It is still possible that a new version of that method will be provided at link-time. Furthermore, the structure of the original program must be preserved for purposes of reflection.
Therefore, we note that a Java compiler cannot expand a method inline at compile time. In general we suggest that implementations use late-bound (run-time) code generation and optimization.
13.5 Evolution of Interfaces
This section describes the impact of changes to the declaration of an interface and its members on pre-existing binaries.
13.5.9 identity
and value
Interfaces
This section might more appropriately appear as 13.5.3, with subsequent sections being renumbered.
If one of the identity
or value
modifiers is added to an interface, a pre-existing binary that attempts to extend or implement the interface may fail to load. Specifically, if the subclass or subinterface has an incompatible identity
or value
modifier, or extends a class or interface with an incompatible identity
or value
modifier, class loading will fail with an IncompatibleClassChangeError
.
Removing one of the identity
or value
modifiers from an interface does not break compatibility with pre-existing binaries.
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 value class or interface type, or a type variable or intersection type bounded by a value class or interface type, or a compile-time error occurs.
A synchronized
statement is executed by first evaluating the Expression. Then:
If evaluation of the Expression completes abruptly for some reason, then the
synchronized
statement completes abruptly for the same reason.Otherwise, if the value of the Expression is
null
, aNullPointerException
is thrown.Otherwise, if the value of the Expression is a reference to a value object, an
IllegalMonitorStateException
is thrown.Otherwise, let the non-
null
value of the Expression be V. The executing thread locks the monitor associated with V. Then the Block is executed, and then there is a choice:If execution of the Block completes normally, then the monitor is unlocked and the
synchronized
statement completes normally.If execution of the Block completes abruptly for any reason, then the monitor is unlocked and the
synchronized
statement completes abruptly for the same reason.
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.8 Primary Expressions
15.8.3 this
The keyword this
may be used as an expression in the following contexts:
in the body of an instance method of a class (8.4.3.2)
in the body of a constructor of a class (8.8.7)
in an instance initializer of a class (8.6)
in the initializer of an instance variable of a class (8.3.2)
in the body of an instance method of an interface, that is, a default method or a non-
static
private
interface method (9.4)
When used as an expression, the keyword this
denotes a value that is a reference either to the object for which the instance method was invoked (15.12), or to the object being constructed. The value denoted by this
in a lambda body (15.27.2) is the same as the value denoted by this
in the surrounding context.
The keyword
this
is also used in explicit constructor invocation statements (8.8.7.1), and to denote the receiver parameter of a method or constructor (8.4).
It is a compile-time error if a this
expression occurs in a static context (8.1.3).
Use of this
in a constructor of a value
class is restricted. It is a compile-time error if a this
expression occurs in the constructor of a value
class unless one of the following is true:
Let C by the innermost enclosing class or interface declaration of a this
expression. If C is generic, with type parameters F1,...,Fn, the type of this
is C<
F1,...,Fn>
. Otherwise, the type of this
is C.
At run time, the class of the actual object referred to may be C or a subclass of C (8.1.5.
Example 15.8.3-1. The this
Expression
class IntVector {
int[] v;
boolean equals(IntVector other) {
if (this == other)
return true;
if (v.length != other.v.length)
return false;
for (int i = 0; i < v.length; i++) {
if (v[i] != other.v[i]) return false;
}
return true;
}
}
Here, the class IntVector
implements a method equals
, which compares two vectors. If the other vector is the same vector object as the one for which the equals
method was invoked, then the check can skip the length and value comparisons. The equals
method implements this check by comparing the reference to the other object to this
.
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, if the class is an identity class, 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 identity 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.
For an identity class, the constructor is provided with the newly-created class instance, and is responsible for fully initializing it. This results in includes 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 When the identity class constructor completes, the value of a the class instance creation expression is a reference to the newly created object of the specified class. Every time the expression is evaluated, a fresh object is created.
For a value class, the constructor itself creates new variables for each of the class's instance fields, and defines a class instance by setting those variables. No superclass constructors are invoked.
When the value class constructor completes, the value of the class instance creation expression is a reference to a class instance with the determined field values. This instance may be the same as an instance created previously.
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 anextends
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)
.
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.