Introduce value objects, which are immutable and lack object identity. Value
objects are distinguished solely by the values of their fields, and can be
represented by Java Virtual Machines in ways that improve performance. This is a
preview language and VM feature.
Enable developers to opt in to a programming model for immutable data in
which the == operator, and all other operations, distinguish objects by
the values of their fields rather than their identities.
Support the compatible migration of existing classes that represent
immutable data to this model. Migrate suitable existing classes in the Java
Platform API, such as Integer and LocalDate, to have value object
instances.
Do not ask developers to learn new semantics for memory management or
variable storage. The Java language should continue to operate on just two
kinds of data: primitives and object references.
Maximize the freedom of JVM implementors to represent immutable data in ways
that improve memory footprint, locality, and garbage collection efficiency.
It is not a goal to automatically treat instances of existing classes as
value objects. Value objects do not uniformly work the same way as other
objects, so class authors must explicitly choose to have value object
instances.
It is not a goal to revise the == operator so that it can be used in place
of the equals method. We redefine == only as much as necessary to cope
with a new kind of identity-free object. The usual advice to compare objects
in most contexts using the equals method still applies.
It is not a goal to introduce a struct feature, as found in the
C and
C#
languages.
It is not a goal to change the treatment of primitive types. Primitives
behave like value objects in many ways, but are a distinct concept.
It is not a goal to guarantee any particular optimization strategy or memory
layout. This proposal enables many optimizations, but we will implement only
some of them initially. Some optimizations, such as layouts that exclude
null, will only become possible after future language and JVM
enhancements.
Many kinds of simple data values are immutable: complex numbers, pixel colors,
times, dates, and so on. We usually model such values with classes that contain
just enough logic to construct, validate, and transform instances, and that
define the equals, hashCode, and toString methods so that equivalent
instances can be used interchangeably.
As an example, the Platform API's LocalDate class models dates:
Intuitively, the essence of a LocalDate object rests in its year, month, and
day values. But in the Java language, the essence of any object is its
identity. Each time the LocalDate.of method invokes new LocalDate(...),
the JVM creates a new object with a unique identity, distinguishable from every
other object in the system.
The easiest way to observe the identity of an object is with the == operator:
Even though d1 and d3 represent the same year-month-day triple — that is,
d1.equals(d3) is true — they are two objects with distinct identities.
For mutable objects, identity is important: It lets us distinguish two objects
that have the same state now but may have different states in the future.
Consider a text-editing application in which lines of text are represented by
instances of a Line class. The sole field of the class is a list of
characters, which is mutated when the user edits the line. Two Line objects
might contain equivalent character lists, and thus also be equivalent, but that
would be a coincidence; when the user changes one of the lines, the application
will mutate the character list of that object but not the other, relying on
identity to mutate the right one.
In other words, when objects are mutable, they cannot be interchangeable — yet
most immutable data values are interchangeable. There is no practical difference
between two LocalDate objects representing 1996-01-23, because their state
is fixed and unchanging. They represent the same value, both now and in the
future. There is no need to distinguish the two objects via their identities.
Object identity is, in fact, actively confusing when objects are immutable and
interchangeable. Most of us can recall the experience of unwittingly using ==
to compare objects, as in d1 == d3 above, and being mystified by a false
result even though the objects' state and behavior seem identical.
Even worse, object identity can expose incidental implementation choices that
result in surprising behavior. For example, the Integer class uses a
cache to avoid creating unnecessary Integer objects with
unique identities. There is, e.g., typically just a single Integer object
representing the value 1. The cache is of fixed size, however, and does not
extend to larger int values such as 1996:
We could avoid this sort of unexpected outcome if objects whose state and
behavior make them interchangeable could be freed from the legacy requirement to
have distinct identities.
The Java language's requirement that every object have identity, whether needed
or not, is a performance impediment. It forces JVMs to allocate memory for each
newly created object, thereby distinguishing that object from every other object
already in the system, and access that memory whenever the object is used.
For example, suppose that a program creates arrays of int values and
LocalDate references:
The int array can be represented by a single block of memory containing int
values:
The LocalDate array, by contrast, must be represented by a block of memory
containing a sequence of pointers, each referencing another block of memory
representing a LocalDate object:
Even though the data represented by the LocalDate array is not significantly
more complex than the int array — a year-month-day triple is effectively 48
bits of primitive data — the memory footprint is far greater because of the
pointers and allocated objects.
To make matters worse, when a program iterates over the LocalDate array, it
may dereference each pointer. Modern CPUs improve performance by caching small
chunks of memory called
cache lines.
The various LocalDate objects could be allocated at memory addresses that are
far apart if, e.g., they were created at different times, or they were moved by
the garbage collector. The resulting poor
reference locality
could degrade performance by requiring every dereference to load a different
cache line from memory.
Out of desperation, we might try to improve performance by writing code that
creates as few objects as possible, thereby de-stressing the garbage collector
and improving reference locality. For example, rather than use LocalDate
objects we could model dates with int values counting the number of days
since 1970-01-01. Unfortunately, this approach gives up the features
of classes that make Java code so maintainable: meaningful names, private state,
data validation by constructors, convenience methods, and so forth. It would be
all too easy to forget — or for a colleague simply not to know — that int
dates are relative to 1970-01-01 rather than some other date, leading to bugs
that are difficult to diagnose.
Trillions of Java objects are created every day, each one bearing a unique
identity. We should enable developers to choose which objects in a program need
identity, and which do not. The author of a class such as LocalDate, which
represents simple immutable data, should be able to opt out of identity. Two
LocalDate objects representing the date 1996-01-23 should be
indistinguishable, just as two int values representing the number 4 are
indistinguishable.
By opting out of identity, developers opt in to a programming model that enables
the best of both worlds: the abstraction of classes, with the simplicity and
performance benefits of primitives.
In the future, this programming model will support new Java Platform APIs, such
as classes that encode different kinds of integers and floating-point values,
and new Java language features, such as user-defined conversions and
mathematical operators for immutable data.
We introduce value objects to model simple immutable data. A value object is
an instance of a value class, declared with the value modifier. Classes
without the value modifier are identity classes, and their instances are
identity objects.
Java programs manipulate objects through references. A reference to an object
is stored in a variable and enables us to find the object's fields.
Traditionally, references are represented in a JVM as pointers to memory
locations, thus encoding the unique identity of each object. Each invocation of
the new operator allocates a fresh object, in a fresh block of memory, and
returns a unique reference. And, traditionally, the == operator compares
references by comparing pointers, so distinct references to two objects are not
== even if the referenced objects are interchangeable.
Value objects are different. A reference to a value object is stored in a
variable and enables us to find the object's fields. In a JVM, however, it might
not be represented by a pointer and thus does not encode the unique identity of
the object. For a value class, invoking the new operator might not allocate a
fresh object; it might, instead, return a reference to an existing object, or
even a reference that embodies the object directly. The == operator compares
references to value objects by comparing the objects' field values, so
references to two objects are == if the objects have identical field values.
We can save memory and improve performance by using value objects for immutable
data. Because a program cannot distinguish two value objects with identical
field values, not even with the == operator, a JVM is able to change how a
value object is laid out in memory without affecting the program. A JVM could,
e.g., store the fields of a value object on the stack or even in CPU registers,
rather than the heap.
To use this feature in JDK NN, you must enable preview features:
Compile the program with javac --release NN --enable-preview Main.java and
run it with java --enable-preview Main; or,
When using the source code launcher, run the program with
java --enable-preview
Main.java; or,
When using jshell, start it with jshell --enable-preview.
Some classes
in the Java Platform API become value classes only when preview features are
enabled; otherwise, they behave just as they did in JDK NN-1.
For example, if your code refers to the LocalDate class and you compile with
preview features disabled, the compiler uses the existing identity-object
version of LocalDate and you do not need to run the program with preview
features enabled. If you compile with preview features enabled, however, the
compiler uses the new value-object version of LocalDate and you must run the
program with preview features enabled. It is not possible to use the
identity-object version of LocalDate when preview features are enabled.
In the Java Platform API, 30 classes are now declared as value classes. Examples
include:
All instances of these classes are value objects. This includes the boxed
primitives that are instances of Integer, Long, and so
forth. The == operator compares value objects by their field values, so, e.g.,
two Integer objects are == if they represent the same primitive value:
Similarly, two LocalDate objects are == if they have the same year, month,
and day values:
We can use the new Objects.hasIdentity method to observe whether an object is
an identity object:
The String class, due to some dependencies on object identity in its API and
implementation, is not a value class, so instances of String are always
identity objects:
In most respects, value objects work the way that objects have always worked in
the language: They have fields and methods, they are handled by reference, and
their references can be null.
A few identity-sensitive operations, however, are not supported by value
objects, including synchronization:
JVM implementors have the freedom to encode references to value objects at run
time in ways that optimize memory footprint, locality, and garbage collection
efficiency. For example, we saw the following array earlier, implemented with
pointers to heap objects:
Now that LocalDate objects lack identity, a JVM need not represent references
to them using pointers; it can, rather, encode the fields of LocalDate objects
directly in the references. Each element of the dates array can be represented
as a 64-bit word that indicates whether the reference is null and, if not,
directly stores the year, month, and day field values of the value object:
The performance characteristics of this LocalDate array may be similar to
those of an ordinary int array, with lower memory footprint and better
reference locality:
This optimization is just one example; some value classes, such as
LocalDateTime, are too large to take advantage of this particular technique.
Still, the lack of identity enables JVM implementors to optimize references to
value objects in many ways.
You can declare your own value classes by applying the value modifier to any
class whose instances should be immutable and interchangeable:
Immutable — All instance fields of the class are final, and the value
represented by an instance will not change over time; and
Interchangeable — It is not necessary to distinguish between two
separately-created instances that represent the same value.
When the value modifier is applied to a class, the fields of the class are
implicitly final. The class itself is also implicitly final, so it cannot be
extended. Because the class is final, its methods cannot be overridden.
There is no restriction on the types of fields in a value class. The fields
may store references to other value objects, or to identity objects, e.g.,
strings.
Record classes are final and all their fields are final, so they
are often good candidates to be value classes:
Many classes have immutable and interchangeable instances, but they cannot be
record classes because their fields do not correspond exactly to their
constructor arguments; i.e., they are not transparent. Such classes might use
private fields internally in a more efficient way than is exposed externally
through public methods. For example, a class might represent a quantity of euros
and cents with a single long field to save memory; it cannot be a value
record, but it can still be a value class:
Traditionally, the purpose of the == operator was to test whether two
referenced objects have the same identity.
After the introduction of value objects, the purpose of the == operator is to
test whether two referenced objects are indistinguishable. Since identity
objects are, by definition, distinguished by their identities, this means that
the == operator works the same for identity objects in Java NN as it has since
Java 1.0: It tests for references to the same object — at the same location in
memory — or for matching null references.
When comparing two value objects, the == operator tests for references to
instances of the same class with the same field values. That is, two value
objects are indistinguishable if:
They are instances of the same value class,
Their primitive-typed fields store the same bit patterns, and
Their reference-typed fields are indistinguishable, applying the ==
operator recursively.
When that is the case, a JVM can freely replace one reference with the other,
and no code will be able to tell the difference.
The == operator and the equals method will often produce the same results
for value objects. For some value classes, however, instances may be
interchangeable (i.e., equals) even if their field values are different (i.e.,
not ==). To test whether two value objects represent the same value, use the
equals method. When declaring a class, define equals in a way that always
returns true for interchangeable instances.
The Substring value class, below, illustrates how == and equals may differ
for some value objects. This class represents a substring of a string without
allocating a new char[] in memory. The internal state of a Substring
instance is a source string and two coordinates, while the value represented by
the instance is a character sequence, as produced by toString. Accordingly,
two instances may represent the same character sequence (i.e., are equals)
even though their internal state is different (i.e., not ==).
The results of the == operator and the equals method may also be different
if the fields of two value objects refer to distinct identity objects that are
interchangeable according to equals:
Another situation in which the == operator and equals may differ is when
value objects have float or double fields. The primitive floating-point
types support multiple NaN values. These NaN values are treated as
interchangeable by most floating-point operations, but because each value is
distinct, value objects that wrap different NaN values are distinguishable by
the == operator. When declaring a value class, you must decide whether that
distinction is meaningful for the equals method. For example, in a value
record, the default behavior of equals treats all NaN values as
interchangeable:
(For more on the different kinds of equivalence between floating-point values,
see the specification of the Double class.)
Because the == operator compares the reference-typed fields of value objects
recursively, applying it to two value objects may require an unbounded number of
comparisons. In the following example, two deep nests of Box objects must be
fully traversed to determine whether the objects are indistinguishable:
Constructors of value classes are constrained, as discussed
below, so that the recursive application
of the == operator to value objects will never cause an infinite loop. But
deep comparisons may take a long time, or even trigger a StackOverflowError.
Every value class belongs to a class hierarchy with java.lang.Object at its
root, just like every identity class. There is no java.lang.Value superclass
of all value classes.
Value classes can implement interfaces. Thus variables declared with an
interface type, or with the type Object, can store references to both value
objects and identity objects:
By default, a value class is implicitly final and cannot be extended. A value
class may, however, be declared abstract, allowing it to be extended by other
classes and have its methods overridden. The fields of an abstract value class
are implicitly final, as in a concrete value class. The methods of an abstract
value class may be marked abstract, as in an abstract identity class.
Declaring an abstract value class is an indication that the class itself has no
need for identity. Its subclasses may be value classes or identity classes. (The
value modifier on an abstract value class can be read as meaning
value-compatible.)
A value class can extend either java.lang.Object or an abstract value class,
but not an identity class. (The Object class is unique in this respect: It is
neither abstract nor a value class, and instances produced by new Object()
have identity, yet it also permits extension by value classes.)
Many existing abstract classes, if they are designed to be publicly extensible,
are good candidates to be abstract value classes. For example, the abstract
class Number has no fields, nor any code that depends on identity-sensitive
features, so it can safely be migrated to an abstract value class:
Integer, which is a value class, and java.math.BigInteger, which is an
identity class, both extend Number:
An abstract value class can be sealed in order
to limit the classes that can extend the class:
Link preview
Structure types - C# reference
Learn about the struct type in C# openjdk.org · learn.microsoft.com
Comments