Use Final
Recently a colleague commented on my use of “final” in method parameters and when defining local variables; I use it elsewhere, too, but these are uses on which he commented. My colleague tried teasing me for wasting my time and tried to suggest it didn’t do what I thought it did. When we were done talking, it was clear it didn’t do what my colleague thought it did.
The “final” keyword has a few different, but similar, uses in Java. All of them have some flavor of “cannot be changed” to them, but really none make an Object immutable unless the final Object is also filled with properties that are immutable after construction.
Class Or Method
When final is used on a class or method, that means that entity cannot be extended. For a class this means another class cannot extend the class, and for a method it means a subclass cannot override the method. Here are a couple examples:
final class CannotExtend{} final class CannotCompile extends CannotExtend{} class CanExtend{ final void cannotOverride(){} } class CannotCompileEither extends CanExtend{ void cannotOverride(){} }
It’s a little bit of overkill, but sometimes there will be final methods in final classes. It’s overkill because once the class is final, all of the methods are final by virtue of the class stopping further extension.
I am in the camp that says that final classes and methods need to be used sparingly as they tend to violate some important ideas of object-oriented programming; specifically, there’s potentially lost opportunity for inheritance or polymorphism.
At the same time I can understand the protection it offers for critical code that must perform as expected no matter where it gets used.
What using final classes and methods gains an application is that the class or method will execute as expected, because it cannot be changed.
Using final on classes does not make immutable classes. At least not simply because of that declaration. This simple class is plenty mutable:
final class PlentyMutable{ private String mutable = null; void setMutable(String mutable){ this.mutable = mutable; } String getMutable(){ return mutable; } }
Variable
When final is used on a variable, whether instance or local or parameter, that variable “cannot change” its value, that bit is true. What it really means to use final on a variable is to say “cannot reassign this variable.”
For a primitive value, making it final will make it immutable, as if now constant.
However, if that variable is an Object, of course, that Object’s properties may be changed, within the limits of the Object in particular. For example, while Boolean and String are Objects, once set its value cannot be changed; however, ArrayList, is also an Object, but its contents are meant to be changed through its methods. Making an ArrayList final does not hamper the use of its methods to change its member variables.
Here’s some simple examples of where final runs into problems.
class UsingFinal{ void finalParameter(final String parameter){ parameter = "Cannot do this!"; // will not compile } void finalLocal(){ final String local = "Have value"; local = "Still cannot do this!"; // will not compile } void finalObject(){ final java.util.ArrayList<String>arrayList = new java.util.ArrayList<String>(); arrayList.add("Can change properties of object"); arrayList = null; // will not compile } }
Using final variables like this is useful as it enforces a kind of “this is meant to stay here” assignment. It helps developers defend against errant reassignment. For example, when accepting a parameter, it ensures that future uses of that variable are the same as the one passed in. Here’s an example of a simple bug that is easily avoided by using final:
class SimpleBug{ String reverse(String reversible){ for(int i=reversible.length(); i>0; i--){ reversible += reversible.charAt(i-1); } return reversible; } }
Instead of reversing our string, a palindrome is returned. Had our parameter been noted as final, we would have immediately realized we needed a second string to accept the reversal of the first.
Another thing that my colleague stumbled on was final instance variables. Final instance variables must be defined in a constructor, or inline as the class is constructed. Further, deferred final instance variables must be defined in all constructors, or in constructors that are called by other constructors, as long as the deferral is eventually satisfied.
class InstanceFinals{ final String definedInline = "This is OK"; final String definedLater; InstanceFinals(){ definedLater = "This is OK"; } InstanceFinals(final String defineLater){ this.definedLater = defineLater; // Works } InstanceFinals(final boolean){ this(); // Works } InstanceFinals(final long defineLater){ this(String.valueOf(defineLater)); // Works } InstanceFinals(final int failsCompilation) { // This will cause the class to fail to compile! } }
Note that the assignment must be done in the constructor and not another method. This will not work:
class ConstructorFinals { final String string; ConstructorFinals(){ setString("string"); } private void setString(String string){ this.string = string; } }
This last one fails because the setString() method isn’t allowed to assign the final variable. This provides some more of that “trying to assign the wrong one” protection as the reversed string method has. By ensuring that final instance variables are assigned only in constructors (or inline), there’s additional protection from oversight and errant reassignment.
Also, as with deferred final instance variables, local variables can be deferred, too. Also, as with final instance variables, final local variables must also be assigned on all branches of execution before the variable is first used. Failure to do so will result in “not assigned” compiler errors.
class UnsatisfiedFinals { String isOne(final int i) { final String finalString; switch (i) { case 1: finalString = "true"; break; } return finalString; // Fails to compile } }
This can be avoided by ensuring the assignment is performed on all branches as this example shows.
class SatisfiedFinals { String isOne(final int i) { final String finalString; switch (i) { case 1: finalString = "true"; break; default: finalString = "false"; } return finalString; } }
This can also be mitigated by ensuring the variable isn’t used after a branch on which it is not assigned.
class AlsoSatisfiedFinals { String isOne(final int i) { final String finalString; switch (i) { case 1: finalString = "true"; return finalString; } return “false”; } }
Using final can seem like overprotectiveness to some. It is certainly misunderstood by many. It does offer some protection by ensuring the intent is correct when reassigning variables; there are valid places for reassigning variables, but sometimes it isn’t what we want.
Can a program run without using final variables, classes, and methods? Absolutely.
Does using final in programs help find or avoid bugs? When done correctly it can.
Final instance variables have one crucial meaning also. They assure that other threads accessing those variables will see only valid values. See http://stackoverflow.com/questions/3974350/safe-publication-through-final
Yes, for the multi-threaded, using final will ensure the final class variables have a value after construction. Be careful, however, because while the variable may have a value, its value may be neither thread-safe or contain valid values. For example, if the final variable is a simple HashSet, it may not contain any useful values, and also is not synchronized so it may have concurrent modification issues.
Nice post 🙂