Java Module 5 – Classes, objects, records and enums
Immutable classes and defensive copies in Java
Build immutable Java classes with final fields and defensive copies, and see why final alone, unmodifiable views and == on values still leak.
What you will learn
- Design immutable classes with final fields, no setters and copies of every mutable part
- Copy mutable inputs and outputs defensively, lists with List.copyOf and arrays with clone()
- Distinguish an unmodifiable view from an unmodifiable copy, and choose between them
- Explain why immutable objects are safe to share between threads, and the condition that guarantee needs
- Compare value-based objects such as Integer with equals, and never use one as a lock
Before you start
What you’ll learn · Before you start
What you will learn
- Design immutable classes with final fields, no setters and copies of every mutable part
- Copy mutable inputs and outputs defensively, lists with List.copyOf and arrays with clone()
- Distinguish an unmodifiable view from an unmodifiable copy, and choose between them
- Explain why immutable objects are safe to share between threads, and the condition that guarantee needs
- Compare value-based objects such as Integer with equals, and never use one as a lock
Before you start
On this page
LeakyTimetable has final fields and no setters, yet a timetable built with 2 stops printed 3 a moment later and 4
after that. An immutable object: An object whose state can never change once it exists, such as a String. Methods that seem to change it, like toUpperCase(), return a new object, so one object can be shared safely by many variables and threads. More in “Strings: first look and equality” is one whose state cannot change after its
constructor has finished. String, Integer and LocalDate are immutable: methods that look like changes, such as
toUpperCase() or plusDays(1), return a new object and leave the old one alone.
Immutable objects cannot surprise you. They are safe as keys in a HashMap (the previous lesson showed what a
changing key does), they can be shared between threads without locks, and code that receives one never has to wonder
who else might change it. This lesson builds such a class, shows in recorded runs how final alone, an unmodifiable
view and == on value objects each let the guarantee slip, and ends with a class for you to fix.
final fixes the reference, not the object it refers to
A final field is assigned exactly once, by its initializer or in the constructor, and never again. The Java
Language Specification is precise about what that does not cover: when a final variable holds a reference to an
object, the state of the object may still change through its own methods, and only the variable stays pointed at the
same object. The same holds for arrays. A final List<String> is a list reference that never moves to another list,
while the list itself can grow, shrink and be cleared by anyone who holds it.
That gap is where most “immutable” classes leak. Here is a timetable with final fields and no setters that still changes after it was built, next to a version that does not:
Java (LeakyTimetable.java) · timetable/LeakyTimetable.java
import java.util.List;
/** Looks immutable: final fields and no setters. It still shares its list with every caller. */
final class LeakyTimetable {
private final String train;
private final List<String> stops;
LeakyTimetable(String train, List<String> stops) {
this.train = train;
this.stops = stops; // keeps the caller's list itself
}
List<String> stops() {
return stops; // hands out the same list
}
@Override
public String toString() {
return train + " " + stops;
}
} Java (Timetable.java) · timetable/Timetable.java
import java.util.ArrayList;
import java.util.List;
/** Immutable: copies the list it is given and never hands out anything a caller can change. */
final class Timetable {
private final String train;
private final List<String> stops;
Timetable(String train, List<String> stops) {
this.train = train;
this.stops = List.copyOf(stops); // an unmodifiable copy, made before anyone else can see the object
}
List<String> stops() {
return stops; // already unmodifiable, so it is safe to return as it is
}
/** "Changing" an immutable object means making a new one. */
Timetable withStop(String stop) {
List<String> longer = new ArrayList<>(stops);
longer.add(stop);
return new Timetable(train, longer);
}
@Override
public String toString() {
return train + " " + stops;
}
} void main() {
List<String> stops = new ArrayList<>(List.of("Pune", "Lonavala"));
LeakyTimetable leaky = new LeakyTimetable("Morning Express", stops);
Timetable safe = new Timetable("Morning Express", stops);
stops.add("Karjat"); // the caller changes its own list afterwards
IO.println("after the caller's change:");
IO.println(" leaky: " + leaky);
IO.println(" safe: " + safe);
leaky.stops().add("Thane"); // anyone who gets the list can change the object
IO.println("after stops().add on leaky: " + leaky);
try {
safe.stops().add("Thane");
} catch (UnsupportedOperationException e) {
IO.println("stops().add on safe: UnsupportedOperationException");
}
Timetable longer = safe.withStop("Mumbai");
IO.println("withStop returns a new object: " + longer);
IO.println("and safe is unchanged: " + safe);
} Output
after the caller's change: leaky: Morning Express [Pune, Lonavala, Karjat] safe: Morning Express [Pune, Lonavala] after stops().add on leaky: Morning Express [Pune, Lonavala, Karjat, Thane] stops().add on safe: UnsupportedOperationException withStop returns a new object: Morning Express [Pune, Lonavala, Mumbai] and safe is unchanged: Morning Express [Pune, Lonavala]
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Main.java
LeakyTimetable changes twice without a single setter. Step through the run and watch which list each reference
leads to:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- The caller's list. main builds an ArrayList holding Pune and Lonavala, and its variable stops refers to it.
- LeakyTimetable keeps it. The LeakyTimetable constructor assigns the argument to its final field. The field now refers to the caller's own list: one list, two references.
- Timetable copies it. The Timetable constructor stores List.copyOf(stops), a separate unmodifiable list, before any other code can see the new object.
- The caller adds Karjat. stops.add("Karjat") changes the ArrayList. leaky now prints three stops, because its field refers to that list; safe still prints two.
- A getter hands it out. leaky.stops() returns the field itself, so stops().add("Thane") changes the timetable again, without a setter: four stops.
- The copy refuses. safe.stops() returns the unmodifiable copy, so the same add throws UnsupportedOperationException and the copy still holds Pune and Lonavala.
- A change is a new object. safe.withStop("Mumbai") copies the stops into a new list, adds Mumbai and builds a new Timetable from it. The original timetable is exactly as it was.
First the caller adds Karjat to the list it passed to the constructor, and the timetable shows it, because its
field holds that very list. Then any code that calls stops() gets the same list again and adds Thane. Timetable
is unaffected by both, and withStop shows how an immutable class offers a change: it returns a new timetable and
leaves the original exactly as it was. The diagram sums up the difference:
A defensive copy gives the object a list of its own
Text description of the diagram
The diagram shows two cases, one above the other.
- Without a copy. The caller's variable stops and the field of LeakyTimetable both point to one ArrayList. When the caller adds Karjat to its list, the timetable sees Karjat too, because there is only one list.
- With List.copyOf. The caller's variable points to its own ArrayList, which now holds Pune, Lonavala and Karjat. The field of Timetable points to an unmodifiable copy made in the constructor, which still holds Pune and Lonavala: the caller's change does not reach it.
An arrow from the first case to the second is labelled "copy in the constructor".
Key idea
final stops a field from being pointed at another object; it does nothing to stop the list or array it points at
from changing.
Checkpoint
Check: What final does and does not protect
Five rules make a class immutable
Each rule closes a different gap, and the table says what goes wrong without it:
| Rule | Without it |
|---|---|
1. Declare the class final |
A subclass adds a mutable field, or overrides stops() to return a list that can change |
2. Make every field private and final |
Code can point a field at another object, and the compiler checks nothing |
| 3. Write no setters; a “change” returns a new object | Any caller can change the state, as with any ordinary class |
| 4. Copy mutable inputs in the constructor | The caller keeps a reference and changes the object later, as it added Karjat |
| 5. Never hand out a mutable part | A caller of a getter changes the object, as it added Thane |
Rule 1 matters because code that accepts a Timetable cannot tell a subclass from the real thing: one subclass with a
mutable field and the guarantee holds for some objects only. Rule 2 lets the compiler help, because final makes it
check that each field is assigned exactly once, in every constructor. Rule 3 is why withStop exists.
Rules 4 and 5 are the defensive copy: A copy a class makes of a mutable object it receives or hands out, so that code outside the class can never change the class's state: List.copyOf(list) in a constructor, array.clone() in a getter. of this lesson’s title: List.copyOf(stops),
array.clone() or the constructor of an immutable type on the way in, and an unmodifiable list, a copy of an array
or an immutable value on the way out. They are defensive because they protect the object from code it cannot
control: callers that keep a reference to what they passed in, and callers that change what they get back. Make the
copy in the constructor, before anything else can see the object.
Checkpoint
Check: The five rules of an immutable class
An unmodifiable view shows later changes, and a copy does not
The JDK offers two ways to make a list that nobody can change, and they behave differently when the original list
changes later. Collections.unmodifiableList(list) returns an unmodifiable view: A wrapper such as Collections.unmodifiableList(list) that refuses every change made through it but reads through to the collection it wraps, so it still shows each change made to that collection.:
its documentation says that queries on it “read through” to the list it wraps, and that only attempts to change the
list through the view are refused. List.copyOf(list) returns an unmodifiable copy: a separate list that does not
reflect later changes to the original.
// What each way of "protecting" a list does when the original list changes later.
void main() {
List<String> original = new ArrayList<>(List.of("Pune", "Lonavala"));
List<String> view = Collections.unmodifiableList(original);
List<String> copy = List.copyOf(original);
List<String> mutableCopy = new ArrayList<>(original);
original.add("Karjat");
IO.println("original: " + original);
IO.println("unmodifiableList: " + view + " (a view: it shows the change)");
IO.println("List.copyOf: " + copy + " (a copy: it does not)");
IO.println("new ArrayList<>(..): " + mutableCopy + " (a copy anyone can change)");
IO.println("view.add throws: " + throwsOnAdd(view));
IO.println("copy.add throws: " + throwsOnAdd(copy));
List<String> fixed = List.of("Pune", "Lonavala");
IO.println("List.copyOf(List.of(...)) is the same object: " + (List.copyOf(fixed) == fixed));
List<String> withNull = new ArrayList<>(Arrays.asList("Pune", null));
IO.println("unmodifiableList keeps a null: " + Collections.unmodifiableList(withNull));
try {
List.copyOf(withNull);
} catch (NullPointerException e) {
IO.println("List.copyOf refuses a null: NullPointerException");
}
}
boolean throwsOnAdd(List<String> list) {
try {
list.add("Thane");
return false;
} catch (UnsupportedOperationException e) {
return true;
}
} Output
original: [Pune, Lonavala, Karjat] unmodifiableList: [Pune, Lonavala, Karjat] (a view: it shows the change) List.copyOf: [Pune, Lonavala] (a copy: it does not) new ArrayList<>(..): [Pune, Lonavala] (a copy anyone can change) view.add throws: true copy.add throws: true List.copyOf(List.of(...)) is the same object: true unmodifiableList keeps a null: [Pune, null] List.copyOf refuses a null: NullPointerException
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Main.java
In the table, “view” is Collections.unmodifiableList(list) and “copy” is List.copyOf(list); “throws” means an
UnsupportedOperationException for changes and a NullPointerException for null elements.
| Criterion | view | copy |
|---|---|---|
| Shows later changes | Yes | No |
| add, set, remove | Throws | Throws |
| null elements | Kept | Throws |
| A new list | No | Usually |
| When to choose | A read-only window on a list you own | Fields and results of immutable classes |
The run’s last copy, new ArrayList<>(list), is a copy too, but one that anyone holding it can change: right for a
private working copy, wrong for a field of an immutable class.
So the view is right only when the class itself keeps changing the list and wants callers to watch, read-only. For
an immutable class it is the wrong tool: whoever still holds the original can change what the view shows.
List.copyOf is the right one, and it is cheap when it can be. Its documentation notes that copying a list that is
already unmodifiable generally makes no copy at all, and the run shows List.copyOf(List.of(...)) returning the
same object. Its one surprise is null: lists made by List.of and List.copyOf refuse null elements, so a
constructor that copies with it also rejects a list that contains null, which is usually what you want anyway.
An unmodifiable list is still only as immutable as its elements. The List documentation warns that if the
elements are mutable, the list’s contents can appear to change. A List<String> copy is fully safe, because
String is immutable; a List<StringBuilder> copy is not.
Checkpoint
Check: Unmodifiable views and unmodifiable copies
Arrays need clone() on the way in and on the way out
An array is always mutable, and final does not change that. The fix is the same two copies, with clone(),
which for an array returns a new array of the same type holding the same elements:
import java.util.Arrays;
/** Fares in paise for each zone. An array is always mutable, so it is copied on the way in and on the way out. */
final class Fares {
private final long[] paise;
Fares(long[] paise) {
this.paise = paise.clone(); // copy in
}
long[] paise() {
return paise.clone(); // copy out: the caller gets an array of its own
}
long fareForZone(int zone) {
return paise[zone];
}
@Override
public String toString() {
return "Fares" + Arrays.toString(paise);
}
} void main() {
long[] input = {1_000, 1_500, 2_500};
Fares fares = new Fares(input);
input[0] = 0; // changing the array that was passed in
long[] out = fares.paise();
out[1] = 0; // changing the array that came back
IO.println(fares + ", zone 1 costs " + fares.fareForZone(1) + " paise");
final long[] constant = {10, 20};
constant[0] = 99; // final stops reassignment, not changes to the elements
IO.println("a final array still changes: " + Arrays.toString(constant));
} Output
Fares[1000, 1500, 2500], zone 1 costs 1500 paise a final array still changes: [99, 20]
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Main.java
The fares stay [1000, 1500, 2500] although the caller zeroed one element of the array it passed in and another of
the array it got back. The last line shows the trap directly: a final array accepts constant[0] = 99.
clone() copies one level only. The documentation of Object.clone calls it a
shallow copy: A copy that copies the references an array or object holds but not the objects they refer to, so the copy and the original share those objects. clone() on an int[][] copies only the outer array of row references. More in “2D and jagged arrays for grids and matrices”: for an array of objects, the new array points to the same objects. That
is enough for arrays of primitives or of immutable values such as String. An array or list of mutable objects needs
each element copied as well, or, better, immutable element types.
The same thinking applies to every mutable type a class keeps. The old java.util.Date is mutable, so a class that
stores one has to copy it in and out; storing a LocalDate or an Instant from java.time instead removes the
problem, because those classes are immutable.
Checkpoint
Check: Copying arrays and other mutable parts
Final fields make an immutable object safe to share between threads
Threads that share a mutable object need locks or other synchronization, or one thread can see another’s change
half done. Immutable objects avoid the problem, and the Java memory model gives final fields a guarantee that
plain fields do not have. Section 17.5 of the specification says that an object is completely initialized when its
constructor finishes, and that a thread which can only see a reference to the object after that point is guaranteed
to see the correct values of its final fields. It also sees the objects and arrays those final fields refer to at
least as up to date as the fields themselves, which covers the copied list inside Timetable. Step through one
timetable handed from one thread to another:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- The constructor runs. The writer calls new Timetable("Morning Express", stops). The constructor copies the list and assigns both final fields.
- The constructor finishes. The specification calls the object completely initialized at this moment. Nothing outside the constructor has seen it yet.
- The reference is shared. The writer stores the reference in a field another thread reads, with no lock and no volatile: a data race.
- The reader takes it. The reader thread reads the field and gets the reference to the timetable.
- The final fields are right. Because the reference was shared only after the constructor finished, the reader is guaranteed to see the right train and stops, and the copied list at least as up to date as the field.
- A plain field gets no promise. Had a field not been final, the same reader could see its default value, 0 or null, although the constructor had assigned it. The specification's own example shows such a 0.
- An escaping this. Had the constructor stored this somewhere other threads read, such as a static list of listeners, before it finished, a reader could see the object half built, final fields included.
The guarantee holds even when the reference reaches the other thread through a
data race: Two accesses to the same variable from different threads, at least one of them a write, that nothing such as a lock or a volatile field puts in order. A thread that reads in a data race may see an old value, or a default such as 0 or null., and it comes with one condition: the constructor must not let this escape,
for example by registering the object as a listener or storing it in a static field, before it finishes. A non-final
field gets no such promise; another thread may see its default value of 0 or null. Timing-dependent behaviour like
that cannot be shown reliably in a recorded run, which is exactly why the guarantee written in the specification
matters: you can rely on it without having observed it.
Checkpoint
Check: Final fields and other threads
Value-based classes are compared with equals, not by identity
Many immutable classes of the JDK, among them Integer, LocalDate and Optional, are
value-based class: A class, such as Integer, LocalDate or Optional, whose equal instances are meant to be interchangeable: code should compare them with equals, never rely on their identity with ==, and never use one as a lock. More in “Wrapper classes, autoboxing and caching”. Their documentation lists what that means: they are final, they
have only final fields, equals, hashCode and toString depend only on their values, and they offer no
constructor you should call (Integer’s are deprecated; you write Integer.valueOf(5) or simply 5). It adds two
warnings. A program should not tell two equal instances apart by identity, for example with ==, and it should not
synchronize on them, because identity-related behaviour may change in a future release.
Java (Main.java) · valuebased/Main.java
void main() {
Integer a = 127, b = 127;
Integer c = 128, d = 128;
IO.println("127 == 127: " + (a == b));
IO.println("128 == 128: " + (c == d));
IO.println("128 equals 128: " + c.equals(d));
} Java (Counter.java) · valuebased/Counter.java
/** Synchronizes on an Integer: javac warns, because value-based objects have no identity you can own. */
final class Counter {
private final Integer lock = 0;
private int count;
void increment() {
synchronized (lock) {
count++;
}
}
} from session import run
# The run, then javac on a class that locks on an Integer (a warning, not an error, so javac still succeeds).
run("""
java valuebased/Main.java
javac -d build valuebased/Counter.java
""") Output
# openjdk version "25.0.4.1" LTS
$ java valuebased/Main.java
127 == 127: true
128 == 128: false
128 equals 128: true
$ javac -d build valuebased/Counter.java
valuebased/Counter.java:7: warning: [identity] attempt to synchronize on an instance of a value-based class
synchronized (lock) {
^
1 warning
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 valuebased_session.py
127 == 127 is true and 128 == 128 is false, while equals is true for both. Integer.valueOf, which
boxing uses, always caches the values from -128 to 127, so the same object comes back for them; outside that range
the default settings create new objects. A comparison with == that works in a test with small numbers fails in
production with larger ones. Compare values with equals.
javac reports the second warning itself. Locking on an Integer compiles, with an [identity] warning that the
object is an instance of a value-based class: another part of the program could hold the same cached object and
lock on it too. Lock on a private final Object lock = new Object() instead.
Comparing boxed numbers with ==
if (paid == due) with two Integer amounts passes every test with amounts up to 127 and fails for 128 and above,
because == compares objects. Write paid.equals(due), or Objects.equals(paid, due) when either may be null.
Checkpoint
Check: Value-based classes, == and locks
Practise: make Order immutable
The checkout service builds Order objects and passes them around between threads. The class keeps the list and
the array its callers give it and hands both out again. Make it immutable without changing its public API: the
same constructor, the same three methods, the same results.
import java.util.List;
/**
* An order as the checkout service builds it. Make it immutable without changing what callers can call:
* the same constructor, the same three methods, the same results.
*/
public class Order {
private String id;
private List<String> items;
private int[] quantities;
public Order(String id, List<String> items, int[] quantities) {
this.id = id;
this.items = items;
this.quantities = quantities;
}
public String id() {
return id;
}
public List<String> items() {
return items;
}
public int[] quantities() {
return quantities;
}
} // Sample checks for Order. Edit Order.java, then run: java order/OrderChecks.java
int passed = 0;
int total = 0;
void check(String what, boolean ok) {
total++;
if (ok) passed++;
IO.println((ok ? "pass " : "FAIL ") + what);
}
boolean refusesAdd(List<String> list) {
try {
list.add("extra");
return false;
} catch (UnsupportedOperationException e) {
return true;
}
}
void main() throws Exception {
List<String> items = new ArrayList<>(List.of("kettle", "mug"));
int[] quantities = {1, 2};
Order order = new Order("A-1009", items, quantities);
items.add("teapot");
quantities[0] = 9;
check("changing the caller's list does not change the order", order.items().equals(List.of("kettle", "mug")));
check("changing the caller's array does not change the order", order.quantities()[0] == 1);
check("items() cannot be changed through the returned list", refusesAdd(order.items()));
order.quantities()[1] = 7;
check("changing the returned array does not change the order", order.quantities()[1] == 2);
check("the class is final", Modifier.isFinal(Order.class.getModifiers()));
boolean allFinal = true;
for (Field f : Order.class.getDeclaredFields()) allFinal &= Modifier.isFinal(f.getModifiers());
check("every field is final", allFinal);
check("the id is still A-1009", order.id().equals("A-1009"));
IO.println(passed + " of " + total + " checks passed");
if (passed < total) System.exit(1);
} Output (exit status 1)
FAIL changing the caller's list does not change the order FAIL changing the caller's array does not change the order FAIL items() cannot be changed through the returned list FAIL changing the returned array does not change the order FAIL the class is final FAIL every field is final pass the id is still A-1009 1 of 7 checks passed
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java OrderChecks.java
Edit Order.java until java order/OrderChecks.java prints 7 of 7 checks passed:
- Make the class
finaland every fieldprivate final. - In the constructor, keep
List.copyOf(items)andquantities.clone()instead of the arguments themselves. - Return the stored list as it is (it is unmodifiable now), and return
quantities.clone()fromquantities().
Then ask what the checks do not test: what happens if a caller passes a list that contains null, and is that
the behaviour you want for an order?
Then work through questions on the six ideas, from final fields to value-based classes. Questions open where your
checkpoint answers were weakest, and the reason behind every choice appears once you answer:
Practice that adapts to you
Practise this lesson
13 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · What makes a Java class immutable?
Its state cannot change after the constructor finishes, and that takes five things. The class is final, so no
subclass can add mutable state or override its methods. Every field is private and final. There are no setters,
and methods that look like changes return a new object, as String.toUpperCase() does. Mutable inputs, such as a
list or an array passed to the constructor, are copied before they are stored, with List.copyOf or clone().
And mutable parts are never handed out: getters return an unmodifiable list or a copy. The point a good answer
makes is that final alone is not enough, because a final field can still refer to a list that someone else
changes. Immutable objects are then safe as map keys and safe to share between threads, since final fields are
guaranteed to be seen correctly once the constructor has finished.
Two longer questions of this module start from this lesson: a configuration class that its author calls immutable, and a price table that one thread replaces every minute while others read it.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
In a code review, a Config class has private final Map<String, String> settings, assigned from the constructor argument, and a getSettings() that returns the field. The author says it is immutable because the field is final and there are no setters. Do you agree? What would you change, and what does each change cost?
-
Mid to senior
A pricing service builds a new immutable PriceTable every minute and stores it in a plain static field; request threads read the field without locks. What exactly does the Java memory model guarantee the readers, what does it not guarantee, and what would you change?
Key takeaways
- An immutable object cannot change after its constructor finishes; “changing” it means making a new object, as
withStopdoes. finalstops a field from being reassigned, and nothing more: the list, array or other object it refers to can still change.- The recipe: a final class, private final fields, no setters, copies of mutable inputs, and no mutable parts handed
out; arrays are copied with
clone(), which is shallow, so prefer immutable elements. Collections.unmodifiableListis a view that shows later changes to the original;List.copyOfis an unmodifiable copy that does not, and it rejectsnullelements.- Final fields of an object whose constructor did not let
thisescape are seen correctly by every thread, which is why immutable objects can be shared without locks. - Value-based classes such as
IntegerandLocalDateare compared withequals, never==, and never used as locks: two boxedIntegerobjects holding 128 are not==.
Terms in this lesson
- data race
- Two accesses to the same variable from different threads, at least one of them a write, that nothing such as a lock or a volatile field puts in order. A thread that reads in a data race may see an old value, or a default such as 0 or null.
- defensive copy
- A copy a class makes of a mutable object it receives or hands out, so that code outside the class can never change the class's state: List.copyOf(list) in a constructor, array.clone() in a getter.
- unmodifiable view
- A wrapper such as Collections.unmodifiableList(list) that refuses every change made through it but reads through to the collection it wraps, so it still shows each change made to that collection.
References
- The Java Language Specification, Java SE 25: 4.12.4 final Variables (Oracle)
- The Java Language Specification, Java SE 25: 17.5 final Field Semantics (Oracle)
- The Java Language Specification, Java SE 25: 17.4.5 Happens-before Order (data races) (Oracle)
- Interface List: unmodifiable lists and List.copyOf (Java SE 25) (Oracle)
- Class Collections: unmodifiableList (Java SE 25) (Oracle)
- Value-based Classes (Java SE 25) (Oracle)
- Class Integer: valueOf and its cache (Java SE 25) (Oracle)
- Class LocalDate (Java SE 25) (Oracle)
- Class Date (Java SE 25) (Oracle)
- Class Object: clone (Java SE 25) (Oracle)
- The javac Command (Java SE 25): -Xlint keys (Oracle)
Report a problem with this lesson
Kept only in this browser. Your Learn progress