The Final Word On the final Keyword
Some features of the Java language simply cannot be ignored.
Consider for example interfaces, used extensively by every Java specification;
or try/catch blocks, that form the basis for exception handling.
Other features are more obscure – useful, but ignored by the masses.
Without looking as far as volatile (probably the most obscure Java keyword),
think about final. When was the last time you used final in your code?
Me, I'm a final fan. Enamored with final, I'm of
the school of thought that inserts final any where, any time.
Or almost. But why?
In this article I shine the spotlight on the Java keyword final, and
invite you to discover its subtle meanings, and the many Java idioms that make use of it. All for
better code!
- The Many Meanings of
final
- Final Variables
- Final Parameters
- Anonymous Local Classes
- Final Fields
- Declare Constants
- Aggregation vs. Acquaintance
- Enforce Atomicity of Object Creation
- Declare Invariants
- For Performance
- Conclusion on Final Fields
- Final Methods
- Enforce Invariant Code
- For Security
- For Performance?
- Final Classes
- Enforce Composition over Inheritance
- For Security
- Immutable Objects
- Conclusion
- References
The Many Meanings of final
The final modifier can be applied to four Java constructs:
- variables: a final variable can be set once and only once.
- fields: a final field can also be set only once, by the constructor
of the class which defines it.
- methods: a final method cannot be overridden nor hidden.
- classes: a final class cannot be extended.
Notice how using final is an entirely negative act.
The final keyword works by subtracting, limiting default language mechanisms:
the ability to override a method, to set a variable or a field.
The motivations behind
using final fall into three broad categories: correctness,
robustness, and finally performance.
Final Variables
A final variable can be set only once, allowing you to declare
local constants. Such a variable can be left un-assigned at the point of declaration,
creating blank finals. But all final variables must be assigned exactly
once.
Final variables come in handy in mostly two situations: to prevent accidental
changes to method parameters, and with variables accessed by anonymous classes.
Final Parameters
The following sample declares final parameters:
public void doSomething(final int i, final int j)
{
// ...
}
final is used here to ensure the two indexes
i
and
j
won't accidentally be reset by the method.
It's a handy way to protect against an insidious bug
that erroneously changes the value of your parameters. Generally speaking,
short methods are a better way to protect from this class of errors,
but final parameters can be a useful addition to your coding
style.
Note that final parameters are not considered part of the method signature,
and are ignored by the compiler when resolving method calls. Parameters can be
declared final (or not) with no influence on how the method is overriden.
Anonymous Local Classes
The second situation involving final variables
is actually mandated
by language semantics. In that situation, the Java compiler won't let you use a variable
unless it is declared final.
This situation arises with closures, also known as anonymous local classes.
Local classes can only reference local variables and parameters that are
declared final.
public void doSomething(int i, int j)
{
final int n = i + j; // must be declared final
Comparator comp = new Comparator()
{
public int compare(Object left, Object right)
{
return n; // return copy of a local variable
}
};
}
The reason for this restriction becomes apparent if we shed
some light on how local classes are implemented.
An anonymous local class can use local variables because the compiler automatically gives
the class a private instance field to hold a copy of each local
variable the class uses. The compiler also adds hidden parameters to each
constructor to initialize these automatically created
private fields. Thus, a local class does not actually access
local variables, but merely its own private copies of them. The only way
this can work correctly is if the local variables are declared final,
so that they are guaranteed not to change. With this guarantee in place, the local
class is assured that its internal copies of the variables accurately reflect
the actual local variables.
Final Fields
A final field can be assigned once and only once, and must be initialized
by every constructor of the class that declares it.
It is also possible to assign the field directly, in the same statement
where it is defined. This simply reflects the fact that such shortcut
assignments are compiled into a synthetic constructor.
E.g. both the following code samples are correct and strictly equivalent;
the first is preferred for being shorter.
public class MyClass
{
private final int i = 2;
}
public class MyClass
{
private final int i;
public MyClass()
{
i = 2;
}
}
Declare Constants
Coupled with static, final is used to flag constants. This usage
is well-known to all Java programmers, so I won't expand much on it.
It is useful to know that the value of a field declared constant in that manner
will be computed statically if possible, at compile-time.
private static final int ERROR_CODE = 1 + 3 * 4 / 2;
public static final String ERROR_MESSAGE = "An error occurred with code=" + ERROR_CODE;
The compiler will compute the value for ERROR_CODE,
concatenate the string equivalent
of the result, and assign the resulting String to ERROR_MESSAGE.
Aggregation vs. Acquaintance
In the words of Gamma et al.'s Design Patterns:
Aggregation implies that one object owns or is responsible for another object.
Generally we speak of an object having or
being part of another object.
Aggregation implies that an aggregate object and its owner have identical
lifetimes.
Acquaintance implies that an object merely knows of another object. Sometimes
acquaintance is called "association" or the "using" relationship. Acquainted
objects may request operations of each other, but they aren't responsible for
each other. Acquaintance is a weaker relationship than aggregation and suggests
much looser coupling between objects.
It's easy to confuse aggregation and acquaintance, because they are often
implemented in the same way. Ultimately, acquaintance and aggregation are
determined more by intent than by explicit language mechanisms.
Aggregation relationships tend to be fewer and more permanent than acquaintance.
Acquaintance, in contrast, are made and remade more frequently, sometimes
existing only for the duration of an operation. Acquaintances are more dynamic
as well, making them more difficult to discern in the source code.
As it turns out, the Java language does offer an explicit mechanism to differentiate
aggregation relationships from mere acquaitances: the object of this article,
the keyword final.
Use it to flag and make explicit aggregations.
But why should this be important to you? The short answer is:
to improve code quality.
Enforce Atomicity of Object Creation
Once a field is determined to be an aggregation of another object,
and it is declared final, an interesting property emerges.
The aggregating object is guaranteed to be created in full,
or it won't be created at all; either all final fields are initialized
successfully, or an exception terminates the constructor.
Say an object Car aggregates another object Engine, and therefore is defined as
absolutely requiring
an Engine instance to function. Declaring the reference to the Engine as final
ensures any Car instance is correctly initialized in full
– or the constructor was terminated abruptly by a thrown exception.
The Car class doesn't even compile without the Engine reference
being initialized.
public class Car
{
private final Engine engine; // always has an engine
public Car()
{
engine = new Engine();
}
}
Simply by tagging a field with final, we have just created a very strong condition
on all Car instances: namely, they must have an Engine to exist. This simple
property can dramatically raise the quality of your code, by
enforcing correct
aggregation relationships between objects. The object thus defined, and
all its aggregated dependents, always exists in a stable state.
Declare Invariants
Design by Contract is an effective programming methodology for designing robust
software components: by declaring (and verifying) conditions specific to a
given component, its behavior can be asserted correct, even at runtime.
final is a great tool to enforce field invariance: since final fields can
only be set once, any attempt to reset their value (accidental or not)
is detected by the compiler.
This idiom is also of great help during refactoring: it catches
refactoring mistakes by acting as a safeguard against the re-initialization of a
field.
A caveat applies here: if a final variable holds a reference to an object,
the object may be modified, in spite of it being final.
This is because final only applies to the reference holding
the object, not the object itself.
The final variable will always refer to the same object, but the object itself may change
through its methods.
This applies also to arrays and collections, because they are both objects. If
a final variable holds a reference to an array, then the components of the array
may be changed by operations on the array, although the variable will always refer to
the same array.
The same restriction applies to collections as well. E.g. a list
may be declared final and thus always exist as far as the aggregating object is concerned, its
content is undetermined, and can be changed at will. Elements can be added/removed
from the collection, even though it is declared final.
For Performance
The revised memory model proposed by JSR 133 includes special provisions for final fields,
provisions that are absent from the existing specification. Newer VMs already implement this
specification, and treat final fields accordingly. Because final fields are assigned exactly once,
aggressive optimizations
in a multithreaded context become possible. Specifically, a field doesn't need to be ever reloaded,
since its value is guaranteed never to change.
Conclusion on Final Fields
Extensive use of final fields leads to a new and interesting programming
idiom. By statically enforcing field initialization at
construction time, objects can be designed to be correct,
fully initialized,
once their construction is complete. Doing so is a simple yet powerful way
to increase both the correctness and robustness of a given object: since it cannot fail to be
correctly initialized, subsequent methods are free to deal with their
own processing, and use whatever fields they need to do said processing,
without concern for the correct initialization sequence of the object.
This idiom strongly relates to eager initialization:
all fields are initialized
as soon as possible, at construction, and never changed once the
initialization phase is over.
In my experience, developers shun eager initialization because
it is perceived as more expensive than lazy initialization. "I don't need this
field until later, so let's not bother with it now," their thinking goes.
Unfortunately, this line
of thinking leads to more complex code than simply initializing all
fields right away. Every usage of the field has to check whether
the field has been initialized, and initialize it if it hasn't. It's akin to premature
optimization, which, as we all know, is the root of all evil.
Compare the two following examples. While it may look like a trivial
transformation, in a real class with potentially dozens of fields,
eager initialization will clear up a lot of code by removing
extraneous tests. By declaring all fields final, initialization is gathered in one place (the constructor),
yielding simpler, more maintainable, code.
public class LazyCar
{
private Engine engine; // lazily initialized
public void drive()
{
if (engine == null)
{
engine = new Engine();
}
// ...
}
}
public class BetterCar
{
private final Engine engine = new Engine(); // using final
public void drive()
{
// the engine is always present
// ...
}
}
Final Methods
A final method is implemented exactly once,
in the declaring class. Such a method cannot be overridden: subclasses cannot substitute a
new definition for the method.
Note that either modifier private or
static also implies final, which is therefore redundant,
when applied to methods. Private and static methods
are always implicitely final, since they cannot be overridden.
Enforce Invariant Code
The Template Method pattern declares an abstract method
solely for the purpose of overriding it in a subclass. This allows the base class
to delegate parts of an algorithm to subclasses.
Final methods cannot be overridden, therefore
they create an almost exact anti-"template method" pattern. But in fact, they are
best used in conjunction with template methods. By specifying explicitely
which parts of the algorithm can vary (using abstract methods) and which cannot
(using final methods), the class's author conveys a precise picture of the work
expected by subclasses.
Final methods are used with template methods to declare the invariant parts of an algorithm.
public abstract class AbstractBase
{
public final void performOperation() // cannot be overridden
{
prepareForOperation();
doPerformOperation();
}
protected abstract void doPerformOperation(); // must override
}
Be aware that final methods impose a very strict restriction on subclass implementors.
In a framework context, think long and hard before declaring methods final, as it will
severely limit the extensibility of the framework, and the possibilities of adapting the
framework to situations unforeseen by the original developers.
For Security
In Java all methods are by default overridable. While this gives maximum flexibility to us
programmers, this liberal attitude can sometimes lead to conflicting situations.
Let's look at the
Object
class for example. It declares methods that certainly must be overridable:
Object.equals
and
Object.toString
are two well-known examples.
But Object also includes methods such as
Object.wait
and
Object.notify
– system-level methods which implement
core language capabilities.
It simply cannot be allowed for
Object.wait
to be substituted by a different implementation. It would alter
the semantics of the language itself.
Final methods come to the rescue again in this case:
Object.wait
is declared
final, and therefore it cannot be changed, accidentally or not. This reasoning also applies to entire
JDK classes, as discussed below.
For Performance?
Since a final method is only implemented in the declaring class, there is no need to dynamically
dispatch a call to a final method, and static invocation can be used instead.
The compiler can emit a direct call to the method, bypassing entirely the usual virtual
method invocation procedure.
Because of this, final methods are also candidates for inlining by a Just-In-Time compiler or a
similar optimization tool. (Remember, private/static methods are already final, therefore always
considered for this optimization.)
Static invocation is faster than dynamic method lookup, leading to the widespread use of final methods
as an optimization technique.
But this "optimization" is next to useless in recent virtual machines: they are able to detect if
a non-final method is overridden, and if not, use static invocation.
Therefore, final should be used first and foremost for sofware engineering reasons, as
discussed in the rest of this article.
Final Classes
A final class cannot be subclassed, or extended, in any way. Final classes can be regarded as a
generalization of final methods: a final class has all its method declared final. On the other
hand, fields of a final class do not have any special property.
Enforce Composition over Inheritance
Since final classes cannot be extended, the only way to reuse them is by composing them with
other objects. And encouraging that practice in your own code
might prove very healthy; inheritance, while a powerful
technique that should not be dismissed, has it own share of issues. It introduces a very tight
coupling between classes, sometimes leading to the infamous
Fragile Base Class problem.
It is also more complex, forcing users
to bounce up and down a class hierarchy in order to understand what a given class does.
And finally, it can break encapsulation by allowing less restrictive access to methods.
Thus final classes are used to enforce composition. This is particularly important with core
classes, classes that define the base functionality of a framework. We look at this case next.
For Security
One of the very best feature of the Java environment is its ability to dynamically load
classes. Necessarily, this flexibility comes at a price, including a more complex
security model. If classes can be loaded dynamically, at any time, the virtual machine must be able
to enforce security policies on the running code.
Final classes are used in this context to prevent malicious code from altering the semantics of classes
essential to the framework.
The best known example of a final class is certainly java.lang.String.
This class is so vital to the operation of the Java compiler and interpreter
that it must be guaranteed that whenever code uses a string,
it gets exactly a java.lang.String and not an instance of some other class.
Because java.lang.String is final, it cannot be subclassed,
none of its methods can be overriden, and therefore any String instance is guaranteed to always
behave the way it is intended.
Immutable Objects
I would like to conclude this article with a section about immutable objects and
what a useful pattern they form.
An immutable object is an object which state is guaranteed to stay
identical over its entire lifetime. While it is perfectly possible to
implement immutability without final, its use makes that
purpose explicit, to the human (the software developer) and the
machine (the compiler).
Immutable objects carry some very desirable characteristics:
- they are simple to understand and easy to use
- they are inherently thread-safe: they require no synchronization
- they make great building blocks for other objects
Clearly final is going to help us define immutable objects. First in labelling our object
as immutable, which makes it simple to use and understand by other programmers. Second
in guaranteeing that the object's state never changes, which enable the thread-safe property:
thread concurrency
issues are relevant when one thread can change data while another thread is reading
the same data. Because an immutable object never changes its data, synchronizing
access to it is not needed.
Create an immutable class by meeting all of the following conditions:
- Declare all fields
private final.
- Set all fields in the constructor.
- Don't provide any methods that modify the state of the object; provide only getter methods (no setters).
- Declare the class final, so that no methods may be overridden.
- Ensure exclusive access to any mutable components, e.g. by returning copies.
Conclusion
I hope you have enjoyed this scrutiny of a sometimes forgotten feature of the Java language.
My references section lists additional resources useful to the reader eager
to keep on learning about final and its uses.
References
About the Author
Renaud Waldura is a software engineer who develops distributed applications in Java.
He relishes in dissecting the subtleties of programming languages, as well as leading
software teams to success on complex projects.
Visit Renaud's Web site at http://renaud.waldura.com
and learn more about how his outstanding Java skills can help you succeed in your business.
Copyright © 2000-2007 by Renaud Waldura.
Permission to make digital or hard copies of part or all of this work for personal
or classroom use is granted without fee, provided that copies are not made or distributed
for profit or commercial advantage, and that copies bear this notice and full citation
on the first page.
Copyright for components of this work owned by others than Renaud Waldura
must be honored. Abstracting with credit is permitted. To copy otherwise, to
republish, to post on servers, or to redistribute to lists, requires prior
specific permission and/or fee. Request permission to publish from
renaud@waldura.com.
Last modified: 2006/03/24 17:24:03 $
|