Java: When to create a final class
A final
class is simply a class that can’t be extended.
This…final class Animal {
…
}
class Dog …prevents this.extends Animal {
…
}
It does not mean that all fields in the class are automatically final
or that all references to this class would act as if they were declared as final
.
When is this useful?
You should make a class final when the alternative—allowing subclassing—opens up for too many complications and becomes error prone.
Allowing subclassing may not seem like a big deal at first, but you should know that it’s notoriously difficult to get inheritance correct, and that it’s better to disallow it altogether than to get it wrong.
Here’s a non-exhaustive list of things to keep in mind when allowing classes to be extended:
-
Implementing
equals
becomes trickySuppose an
Animal
has a single property:name
. Should anAnimal
with name “Fido” equal aDog
with name “Fido”?@Override boolean equals(Object o) { return o instanceof Animal && this.name.equals(((Animal) o).name); }
Seems reasonable, no?
equals
however, must by contract, be reflexive: ifx
equalsy
, theny
must equalx
. In other words, theDog
object must thenequal
theAnimal
object too. But what ifDog
has an additional propertybreed
that anAnimal
lacks? There’s no obvious solution here! Angelika Lagner and Klaus Kreft dedicate a whole article to this subject: Secrets of equals(). -
Seemingly innocent overrides can have surprising effects
If a private method class calls a public method in the base class, then overriding the public method may have unexpected side-effects on the inner workings of the base class.
-
Class invariants may break
The base class perhaps maintains certain internal invariants. Perhaps it’s immutable, or intended to be thread-safe. There’s no way to enforce such design decisions upon subclasses. If you receive an object of the base class type, you technically can’t assume it’s immutable or thread-safe unless it’s
final
.
Put differently: Class inheritance is a flexible language feature, and a powerful footgun.
Concluding with a quote from Effective Java:
Item 19: Design and document for inheritance or else prohibit it
[…] designing a class for inheritance requires great effort and places substantial limitations on the class.
[…] The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. Joshua Bloch