分享

Renaud Waldura‘s The Final Word On the final Keyword

 cherishchen 2006-10-07

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!

  1. The Many Meanings of final
  2. Final Variables
    1. Final Parameters
    2. Anonymous Local Classes
  3. Final Fields
    1. Declare Constants
    2. Aggregation vs. Acquaintance
    3. Enforce Atomicity of Object Creation
    4. Declare Invariants
    5. For Performance
    6. Conclusion on Final Fields
  4. Final Methods
    1. Enforce Invariant Code
    2. For Security
    3. For Performance?
  5. Final Classes
    1. Enforce Composition over Inheritance
    2. For Security
  6. Immutable Objects
  7. Conclusion
  8. References

The Many Meanings of final

The final modifier can be applied to four Java constructs:

  1. variables: a final variable can be set once and only once.
  2. fields: a final field can also be set only once, by the constructor of the class which defines it.
  3. methods: a final method cannot be overridden nor hidden.
  4. 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 that 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:

  1. Declare all fields private final.
  2. Set all fields in the constructor.
  3. Don‘t provide any methods that modify the state of the object; provide only getter methods (no setters).
  4. Declare the class final, so that no methods may be overridden.
  5. 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. and learn more about how his outstanding Java skills can help you succeed in your business.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多