The final keyword


Desktop-as-a-Service Designed for Any Cloud ? Nutanix Frame

Bruce Eckel's Thinking in Java Contents | Prev | Next

Final data

  1. It can be a compile-time constant that won’t ever change.
  2. It can be a value initialized at run-time that you don’t want changed.
//: FinalData.java
// The effect of final on fields
class Value {
  int i = 1;
public class FinalData {
  // Can be compile-time constants
  final int i1 = 9;
  static final int I2 = 99;
  // Typical public constant:
  public static final int I3 = 39;
  // Cannot be compile-time constants:
  final int i4 = (int)(Math.random()*20);
  static final int i5 = (int)(Math.random()*20);
  Value v1 = new Value();
  final Value v2 = new Value();
  static final Value v3 = new Value();
  //! final Value v4; // Pre-Java 1.1 Error: 
                      // no initializer
  // Arrays:
  final int[] a = { 1, 2, 3, 4, 5, 6 };
  public void print(String id) {
      id + ": " + "i4 = " + i4 + 
      ", i5 = " + i5);
  public static void main(String[] args) {
    FinalData fd1 = new FinalData();
    //! fd1.i1++; // Error: can't change value
    fd1.v2.i++; // Object isn't constant!
    fd1.v1 = new Value(); // OK -- not final
    for(int i = 0; i < fd1.a.length; i++)
      fd1.a[i]++; // Object isn't constant!
    //! fd1.v2 = new Value(); // Error: Can't 
    //! fd1.v3 = new Value(); // change handle
    //! fd1.a = new int[3];
    System.out.println("Creating new FinalData");
    FinalData fd2 = new FinalData();
} ///:~ 

Since i1 and I2 are final primitives with compile-time values, they can both be used as compile-time constants and are not different in any important way. I3 is the more typical way you’ll see such constants defined: public so they’re usable outside the package, static to emphasize that there’s only one, and final to say that it’s a constant. Note that final static primitives with constant initial values (that is, compile-time constants) are named with all capitals by convention. Also note that i5 cannot be known at compile time, so it is not capitalized.

fd1: i4 = 15, i5 = 9
Creating new FinalData
fd1: i4 = 15, i5 = 9
fd2: i4 = 10, i5 = 9

Note that the values of i4 for fd1 and fd2 are unique, but the value for i5 is not changed by creating the second FinalData object. That’s because it’s static and is initialized once upon loading and not each time a new object is created.

The variables v1 through v4 demonstrate the meaning of a final handle. As you can see in main( ), just because v2 is final doesn’t mean that you can’t change its value. However, you cannot re-bind v2 to a new object, precisely because it’s final. That’s what final means for a handle. You can also see the same meaning holds true for an array, which is just another kind of handle. (There is know way that I know of to make the array handles themselves final.) Making handles final seems less useful than making primitives final.

Blank finals
//: BlankFinal.java
// "Blank" final data members
class Poppet { }
class BlankFinal {
  final int i = 0; // Initialized final
  final int j; // Blank final
  final Poppet p; // Blank final handle
  // Blank finals MUST be initialized
  // in the constructor:
  BlankFinal() {
    j = 1; // Initialize blank final
    p = new Poppet();
  BlankFinal(int x) {
    j = x; // Initialize blank final
    p = new Poppet();
  public static void main(String[] args) {
    BlankFinal bf = new BlankFinal();
} ///:~ 

You’re forced to perform assignments to finals either with an expression at the point of definition of the field or in every constructor. This way it’s guaranteed that the final field is always initialized before use.

Final arguments
//: FinalArguments.java
// Using "final" with method arguments
class Gizmo {
  public void spin() {}
public class FinalArguments {
  void with(final Gizmo g) {
    //! g = new Gizmo(); // Illegal -- g is final
  void without(Gizmo g) {
    g = new Gizmo(); // OK -- g not final
  // void f(final int i) { i++; } // Can't change
  // You can only read from a final primitive:
  int g(final int i) { return i + 1; }
  public static void main(String[] args) {
    FinalArguments bf = new FinalArguments();
} ///:~ 

Note that you can still assign a null handle to an argument that’s final without the compiler catching it, just like you can with a non-final argument.

The methods f( ) and g( ) show what happens when primitive arguments are final: you can only read the argument, but you can't change it.

Final methods

The second reason for final methods is efficiency. If you make a method final, you are allowing the compiler to turn any calls to that method into inline calls. When the compiler sees a final method call it can (at its discretion) skip the normal approach of inserting code to perform the method call mechanism (push arguments on the stack, hop over to the method code and execute it, hop back and clean off the stack arguments, and deal with the return value) and instead replace the method call with a copy of the actual code in the method body. This eliminates the overhead of the method call. Of course, if a method is big, then your code begins to bloat and you probably won’t see any performance gains from inlining since any improvements will be dwarfed by the amount of time spent inside the method. It is implied that the Java compiler is able to detect these situations and choose wisely whether to inline a final method. However, it’s better to not trust that the compiler is able to do this and make a method final only if it’s quite small or if you want to explicitly prevent overriding.

Final classes

//: Jurassic.java
// Making an entire class final
class SmallBrain {}
final class Dinosaur {
  int i = 7;
  int j = 1;
  SmallBrain x = new SmallBrain();
  void f() {}
//! class Further extends Dinosaur {}
// error: Cannot extend final class 'Dinosaur'
public class Jurassic {
  public static void main(String[] args) {
    Dinosaur n = new Dinosaur();
    n.i = 40;
} ///:~ 

Note that the data members can be final or not, as you choose. The same rules apply to final for data members regardless of whether the class is defined as final. Defining the class as final simply prevents inheritance – nothing more. However, because it prevents inheritance all methods in a final class are implicitly final, since there’s no way to override them. So the compiler has the same efficiency options as it does if you explicitly declare a method final.

Final caution

The standard Java library is a good example of this. In particular, the Vector class is commonly used and might be even more useful if, in the name of efficiency, all the methods hadn’t been made final. It’s easily conceivable that you might want to inherit and override with such a fundamentally useful class, but the designers somehow decided this wasn’t appropriate. This is ironic for two reasons. First, Stack is inherited from Vector, which says that a Stack is a Vector, which isn’t really true. Second, many of the most important methods of Vector, such as addElement( ) and elementAt( ) are synchronized, which as you will see in Chapter 14 incurs a significant performance overhead that probably wipes out any gains provided by final. This lends credence to the theory that programmers are consistently bad at guessing where optimizations should occur. It’s just too bad that such a clumsy design made it into the standard library where we must all cope with it.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date