Java Record Classes and Their Components
Declaring a class final means no subclass can exist, so the compiler knows exactly what each method call does and can optimize generated code accordingly.
Try it: Names and Objects
How Python variables are names bound to objects: assignment never copies, mutating a list is seen through every name that points at it, and ints are replaced rather than changed.
How it works
- name = object binds a name; b = a makes b point at the same object.
- Mutating a list (append, +=) changes the one shared object.
- a = a + [x] and y = y + 1 create NEW objects and re-point one name.
- Passing a list to a function passes the reference, so the function can change it.
Default run (7 steps): Two names, one list. Every name is a label that points at an object. … Printed: [1, 2, 3] / True
Simplified: Six fixed scripts on a tiny heap; you choose the values. Object numbers are illustrative, not real id() values.
Loading the simulation…
Asking Whether Final and Immutable Are Just Conventions
Record classes in Java are usually declared final and are built around values that cannot change after they are created. A reasonable first reaction is to ask whether these choices are just conventions or whether they actually buy something. This article takes that question seriously. It looks at two properties, finality and immutability, and asks what each one makes possible for the compiler and for the programmer who must read and trust the code.
The answer has two parts. Finality helps the compiler produce better code. Immutability helps people reason about programs. Keeping those two benefits separate makes each one easier to understand, so this article handles them one at a time. Both, however, are consequences of one underlying fact: the state of the object is fixed once it exists.
Two Properties That Are Easy to Confuse
Finality: declaring a class final means no subclass can exist. Because of that, the compiler knows exactly what each method call on that class does, as defined by the class itself.
Immutability: an immutable object's state cannot change after creation. This may help optimization, but its main value is that programs become easier to reason about.
Finality is about what kinds of objects can exist at run time: none that override the class's behavior. Immutability is about whether the values inside one object can change over time. A class can have one property without the other, so the two should be evaluated separately.
What a Method Call Depends On
Consider a call such as value.plus(other) where the declared type of value is a class that is not final. At compile time, the compiler only knows the declared type. The actual object at run time might be an instance of a subclass that has overridden plus(). Which code runs can therefore depend on what the object turns out to be when the program executes, and the compiler cannot settle that question ahead of time.
A final class removes that uncertainty. If no subclass can exist, a call to a method on that class does exactly what the class's own definition says. Knowing the exact behavior of each call is what allows the compiler to rewrite code in ways that would be unsafe otherwise. The compiler can optimize code generated for final classes in ways it cannot apply when subclasses might change the meaning of a call.
Tracing Temporary Objects in a Quadratic
Complex numbers appear often in arithmetic, and a quadratic expression of the form A times x squared plus B times x plus C can be built from Complex objects, with A, B, C, and x each being a Complex value. Evaluating that expression means calling operations such as times() and plus() repeatedly, and each of those operations returns a new object. The source's example shows that a quadratic evaluation of this kind creates five objects, four of which are scratch work along the way.
Notice that every call to times() or plus() returns a fresh Complex. Nothing is changed in place. That is what makes the intermediate results separate objects, and it is why each step in the chain is a value the program creates and then passes on to the next step.
Counting the objects in A x squared plus B x plus C
Evaluate A.times(x.times(x)).plus(B.times(x)).plus(C) and count the Complex objects created along the way.
- 1Step 1
x.times(x)creates the first object, the value x squared. - 2Step 2
A.times(x squared)creates the second object, A times x squared. - 3Step 3
B.times(x)creates the third object, B times x. - 4Step 4
The first
plus()adds the second and third objects, creating the fourth object. - 5Step 5
The second
plus()adds C, creating the fifth object, which is the final result.
Five objects are created. Four of them are intermediate values that the program only uses to build the last one, matching the count in the source.
The before view shows the program as written: four scratch values, each used once, feed into the result. The after view shows what the compiler may be able to keep: only the final result remains as a value the program needs to return. The source says the compiler can avoid the scratch work because, for a final class, it knows precisely what each call does. Whether it does so for a particular program is a compiler decision, not something the code guarantees by itself.
Keeping a Property True Over Time
Immutability lets a programmer reason about one object at a single moment and then keep reasoning about it later. If a property is verified at one point in time on an immutable object, that property remains true, because nothing can modify the object afterward. A programmer checking that the real part of a Complex value is zero does not need to recheck it at every later use of that same object.
In the Complex class above, each field is assigned once in the constructor and never reassigned. From construction onward the value of re and im is fixed. The property a reader cares about, such as the imaginary part being zero, is therefore settled at the moment the object is built and stays settled for the object's whole life.
Common Misreadings of Finality and Immutability
Common mistake Treating a final class as automatically immutable
Why it failsFinality is about subclasses. It says nothing about whether the object's values can change after creation.
Do this insteadCheck the fields and their assignments separately to decide whether the object's state is fixed.
Common mistake Believing a final field makes the whole object immutable
Why it failsA final field means the variable is assigned once. Whether the object it refers to can still change depends on that object's own design.
Do this insteadAsk whether the object's own fields can change, not only whether the reference is final.
Common mistake Assuming immutability alone lets the compiler remove temporary objects
Why it failsThe source ties the compiler's knowledge of each call's exact behavior to the class being final, not only to the object being unchanging.
Do this insteadAssociate the optimization benefit with finality, and the reasoning benefit with immutability.
Common mistake Thinking a non-final class can be optimized just as aggressively
Why it failsIf a subclass could override
plus()ortimes(), the compiler cannot know which body runs until run time.Do this insteadExpect fewer guarantees for non-final classes, because subclass objects may redefine the methods.
| Property | What it guarantees | What it leaves changeable | Main beneficiary |
|---|---|---|---|
| Final class | No subclass exists, so each method call has a known meaning | The values inside an object may still change, depending on its fields | The compiler |
| Final field | The variable is assigned only once | The state of the object the variable refers to | The reader checking assignments |
| Immutable object | Its state cannot change after creation | Nothing about its state after construction | The programmer reasoning about properties over time |
Finality and immutability make different promises and help different readers.
Checking Your Prediction About Subclasses
What do you think happens?
Suppose Complex were not declared final, and a subclass overrode plus(). Before the program runs, can the compiler know which plus() body will execute for a call on a value declared as Complex?
Reveal answer
No, the actual object's class may only be known at run time
The declared type is only part of the picture. If a subclass can exist and override plus(), the body that runs depends on the object the program actually holds when it executes, and the compiler cannot settle that ahead of time.
Using the Complex class, explain in two or three sentences why making the class final lets the compiler reason about a call to times(), and separately explain which property a reader gains from the fact that re and im are assigned only in the constructor.
Hints
- Connect the first part to the absence of subclasses.
- Connect the second part to the state being fixed from construction onward.
Key Takeaways
- A final class means no subclass can exist, so the compiler knows exactly what each method call does and may optimize generated code accordingly.
- For a non-final class, a method call's behavior can depend on a subclass object seen only at run time, so the compiler cannot settle it ahead of time.
- Repeated calls on value-style objects such as Complex create intermediate objects; a final class lets the compiler potentially remove scratch work like the four scratch objects in the quadratic example.
- Immutability means an object's state is fixed after creation, so a property verified at one point stays true, mainly helping programmers reason about code.
- Finality and immutability are separate properties: a final class is not automatically immutable, and a final field does not by itself make an object immutable.