final vs finally vs finalize — three different things
Easyfinal is a modifier: a variable assigned once, a method that can't be overridden, a class that can't be extended. finally is the block that runs after try/catch however it exits. finalize() was a GC callback, deprecated for removal since Java 18.
How it works
final (keyword)
- On a variable or field: it can be assigned exactly once. For an object reference, the reference is fixed; the object can still change.
- On a method: subclasses can't override it.
- On a class: nobody can extend it (
String,Integer, everyrecord). finalfields also get a memory-model guarantee: once the constructor finishes, other threads see their values without extra synchronization (as long asthisdidn't leak out during construction).
finally (block)
- Attached to
try. It runs whether thetrycompletes normally, returns, or throws, after any matchingcatch. - Used for cleanup, though try-with-resources is now the preferred way to close things.
finalize() (method)
- A method on
Objectthat the garbage collector might call before reclaiming an object. - No guarantee when, or whether, it runs. It slows down GC and can even resurrect objects.
- Marked
@Deprecatedback in Java 9; JEP 421 in Java 18 went further and flagged finalization as slated to be removed entirely. Use try-with-resources, orjava.lang.ref.Cleaneras a safety net.
Example
final class Invoice { // can't be subclassed
private final List<String> lines = new ArrayList<>();
void add(String l) { lines.add(l); } // fine: the list is mutable, the reference isn't
}
static int attempts() {
int n = 1;
try {
return n; // value 1 is captured here
} finally {
n = 99; // runs, but doesn't change the returned value
System.out.println("cleanup");
}
} // returns 1
// Preferred over finally for resources
try (var in = Files.newBufferedReader(Path.of("orders.csv"))) {
System.out.println(in.readLine());
} // in.close() runs automaticallyEdge cases
finallydoesn't run if the JVM stops first:System.exit(), a crash, or the process being killed. It also never runs if thetryloops forever.- A
returninsidefinallyoverrides thetry's return value and silently discards any exception in flight. - A local variable used inside a lambda must be final or effectively final (never reassigned), even without the keyword.
- A
finalfield must be assigned exactly once in every constructor path, or the class doesn't compile.
Common mistakes
- Believing
final List<X>makes the list immutable. UseList.of(...)orCollections.unmodifiableListfor that. - Returning or throwing from
finallyand hiding the real error. - Overriding
finalize()to close files or sockets. It may never run.
Likely follow-up
"What replaces finalize for native resources?" Implement AutoCloseable and use try-with-resources. If you need a backstop for users who forget, register a cleanup action with Cleaner, making sure it doesn't hold a reference back to the object itself.
Get every deep dive in the app
Coming soon to the App StoreComing soon to Google Play