Jump to content

Final (Java)

From Alfawiki

In the context of the Java language, the final keyword designates an element that can be assigned a value only once.

After a final variable receives its initial assignment, it retains that value permanently. When referring to an object, although the object's internal state may change, the variable itself consistently points to the same instance—this behavior is known as non-transitivity. The same principle applies to arrays: a final array reference cannot be redirected, but the contents within the array remain modifiable.

Final classes

A final class is one that cannot be extended by subclassing. This limitation contributes to enhanced security and performance, which explains why numerous fundamental Java classes such as and are declared final.

Example: <syntaxhighlight lang="java"> public final class MyFinalClass {...}

public class ThisIsWrong extends MyFinalClass {...} // not allowed </syntaxhighlight>

Final methods

A method marked as final cannot be overridden or hidden in subclasses. This restriction safeguards essential behavior from being altered by derived classes.

Example: <syntaxhighlight lang="java"> public class Base {

   public       void m1() {...}
   public final void m2() {...}
   public static       void m3() {...}
   public static final void m4() {...}

}

public class Derived extends Base {

   public void m1() {...}  // allowed, overrides Base#m1()
   public void m2() {...}  // disallowed
   public static void m3() {...}  // allowed, hides Base#m3()
   public static void m4() {...}  // disallowed

} </syntaxhighlight>

It is a common misconception that declaring a method as final automatically improves performance through inlining. Since methods are loaded during runtime, only the runtime system and the JIT compiler determine when method inlining occurs, regardless of whether methods are final.

An exception applies to machine code compilers employing static linking, which can safely inline methods computable at compile time.

Final variables

A final variable must be assigned exactly once, either at declaration or later. Variables declared as "blank finals" are those not initialized at declaration; such instance variables must be assigned within each constructor of their class, while blank final static variables require assignment inside a static initializer block—omission leads to a compilation error. For reference-type final variables, reassignment is prohibited, though the referenced object's internal state can still be changed if mutable.

Unlike constants, values of final variables are not always determined during compilation. Conventionally, final constants are represented using uppercase letters with underscores.

Example: <syntaxhighlight lang="java"> public class Sphere {

   // Pi represents a universal constant.
   public static final double PI = 3.141592653589793;
   public final double radius;
   public final double xPos;
   public final double yPos;
   public final double zPos;
   Sphere(double x, double y, double z, double r) {
       radius = r;
       xPos = x;
       yPos = y;
       zPos = z;
   }
   [...]

} </syntaxhighlight>

Attempting to reassign radius, xPos, yPos, or zPos results in a compile-time error. Assignments to final variables outside their constructors are likewise prohibited by the compiler.

To demonstrate that declaring variables as final does not guarantee immutability, consider substituting individual position variables with a single object:

<syntaxhighlight lang="Java">

   public final Position pos;

</syntaxhighlight>

In this case, while pos cannot be reassigned to another object, its fields (pos.x, pos.y, pos.z) remain mutable unless they themselves are also declared final.

Utilizing final variables supports optimization strategies. For instance, knowing that a Sphere's radius remains constant allows volume calculations to be memoized, enhancing performance when such computations occur frequently. Declaring radius as final signals both developers and compilers about this optimization opportunity.

An unusual yet valid usage pattern is:

<syntaxhighlight lang="Java"> for (final SomeObject obj : someList) {

  // operations on obj

} </syntaxhighlight>

Since obj is redeclared with each iteration and ceases to exist after each loop cycle, this single identifier corresponds to multiple distinct variables over the loop's duration.

Final variables within nested objects

Using final variables facilitates the creation of immutable object hierarchies, guaranteeing that objects remain unchanged after their instantiation. To qualify as immutable, a class must exclusively contain final fields whose types are themselves immutable; this includes Java primitives, strings, and certain other classes.

However, if any object within the hierarchy is mutable, declaring its reference as final does not ensure the immutability of the accessible objects. For instance, a coordinate origin represented by java.awt.Point—which exposes public fields that can be modified—can still be altered even when referenced by a final variable.

Marking a variable as final restricts reassignment of the reference but does not prevent changes to the object's internal state. Therefore, for an object to be truly immutable, all its fields must be declared final and must be of immutable types, excluding mutable classes such as java.util.Date and java.awt.Point.

Final variables and inner classes

Within methods, anonymous inner classes have access only to variables declared as final in the enclosing method’s scope. For primitive types, this means their values remain constant; for objects, the reference remains unchanged. This enables the compiler to capture these values during runtime and embed copies within the inner class instance. Consequently, once the outer method completes and its stack frame is discarded, the inner class continues to maintain its own copy of these variables.

As an example, in graphical user interface development, declaring a JFrame variable as final permits safe access inside an anonymous inner class assigned to handle event processing, thereby supporting thread-safe updates to the UI.

Blank final variables

The concept of a blank final was introduced in Java 1.1; it refers to a final variable declared without an immediate initializer. Unlike earlier Java versions where final variables had to be initialized at declaration, blank finals allow a single assignment at a later point. The compiler enforces this through flow analysis, ensuring that each blank final is assigned exactly once before use; failure to comply results in a compile-time error.

<syntaxhighlight lang="java"> final boolean hasTwoDigits; if (number >= 10 && number < 100) {

   hasTwoDigits = true;

} if (number > -100 && number <= -10) {

   hasTwoDigits = true; // compile-time error: possible multiple assignment.

} </syntaxhighlight>

Moreover, blank finals must be definitely assigned before they are accessed.

<syntaxhighlight lang="java"> final boolean isEven;

if (number % 2 == 0) {

   isEven = true;

}

System.out.println(isEven); // compile-time error: unassigned in else branch. </syntaxhighlight>

It is important to note that non-final local variables also require definite assignment prior to usage.

<syntaxhighlight lang="java"> boolean isEven; // not declared final

if (number % 2 == 0) {

   isEven = true;

}

System.out.println(isEven); // identical compile-time error due to missing assignment. </syntaxhighlight>

C/C++ equivalent of final variables

In C and C++, the counterpart to Java's final keyword is const, which applies to data types rather than directly to variables. Although constness can be circumvented using casting techniques, modifying objects originally declared const leads to undefined behavior. In contrast, Java's final enforces strict immutability at compile time, albeit reflection mechanisms can occasionally bypass this restriction—primarily during deserialization processes.

C and C++ further differentiate between pointers that are themselves const and pointers referencing const data, allowing precise control over mutability similar to Java’s treatment of final references.

Additionally, in C++, the keyword final signifies that a class cannot be subclassed or that an overridden method cannot be further overridden:

<syntaxhighlight lang="C++"> // Class marked final cannot be inherited class Z final : public X, public Y { public:

   // Method marked final cannot be overridden again
   void someOperation() override final {
       // implementation details
   }

}; </syntaxhighlight>

C# equivalents to the final keyword

C# exhibits many parallels with Java regarding syntax and language capabilities: Java operates on the JVM, whereas C# relies on the .Net Framework; Java compiles into bytecode, while C# generates MSIL; both languages do not support pointers.

In place of Java's final keyword, C# offers:

  • sealed to restrict inheritance for classes and methods
  • readonly to declare variables that can only be assigned once

An important difference lies in C#'s const, which represents a compile-time constant, in contrast to readonly, whose value can be assigned during runtime, enabling initialization later in the program execution.

See also