Is a Java Immutable Class Always final?

In response to my recent blog posting Immutable Java Objects, Matt brought up a good point of discussion related to making Java classes truly immutable by declaring them as final so that they cannot be extended. His comment was:

"Implementation inheritance explicitly disallowed." -- isn't this largely orthogonal to the question of immutability?

In that previously referenced blog post, I listed several resources that explain how to write immutable Java classes. Each of these resources recommends making the Java class final as one of the steps in making it immutable. The resources with the recommendation include Brian Goetz's To Mutate or Not to Mutate?, the Java Tutorial section on A Strategy for Designing Immutable Objects, and Joshua Bloch's Effective Java.

A similar question to this is asked in the StackOverflow thread immutable class should be final?: "So why is immutability a good reason for making a class final?" In this blog post, I look a little more deeply into this issue.

The CERT Secure Coding Standards contains the entry OBJ38-J. Immutable classes must prohibit extension which succinctly describes a primary rationale for specifying an immutable Java class as final:

By declaring immutable classes final, we impose a guarantee that malicious subclasses capable of changing the state of the object and violating assumptions that clients often make regarding immutability, are not a concern.

In Immutable Objects in Java, the four authors (Haack, Poll, Schafer, and Schubert) include a section on "enforcing immutability." In this section they maintain that classes which extend immutable classes must also themselves be immutable. The authors suggest that making the immutable class final would be one way to deal with a situation in which "malicious code may try to subclass an immutable class" and "masquerade" as its parent type,

In Mutable and Immutable Objects, David O'Meara specifies that there are two approaches for ensuring methods are not overridden in a class designed to be immutable. He differentiates between strong immutability (class is made final) and weak immutability (methods are individually and explicitly declared final rather than declaring class final). In O'Meara's words, "The preferred way is to make the class final" (strong immutability).

With the above background details in mind, it's time to move on to some code examples.

The following Fraction class has some traits of immutability (such as its fields being final and private):

Fraction.java

package dustin.examples;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * Example of almost immutable class.
 */
public class Fraction
{
   /** Fraction's numerator. */
   private final long numerator;

   /** Fraction's denominator. */
   private final long denominator;

   /** Scale used in BigDecimal division. */
   private final int scale;

   /**
    * Parameterized constructor accepting numerator and denominator for the
    * fraction represented by me.
    *
    * @param newNumerator Numerator of fraction.
    * @param newDenominator Denominator of fraction.
    */
   public Fraction(final long newNumerator, final long newDenominator)
   {
      this.numerator = newNumerator;
      this.denominator = newDenominator;
      this.scale = 25;
   }

   /**
    * Parameterized constructor accepting numerator and denominator for the
    * fraction represented by me along with a scale for my decimal representation.
    *
    * @param newNumerator Numerator of fraction.
    * @param newDenominator Denominator of fraction.
    * @param newScale Scale of my decimal representation.
    */
   public Fraction(final long newNumerator, final long newDenominator, final int newScale)
   {
      this.numerator = newNumerator;
      this.denominator = newDenominator;
      this.scale = newScale;
   }

   /**
    * Provide this fraction's numerator.
    *
    * @return Numerator of this fraction.
    */
   public long getNumerator()
   {
      return this.numerator;
   }

   /**
    * Provide this fraction's denominator.
    *
    * @param Denominator of this fraction.
    */
   public long getDenominator()
   {
      return this.denominator;
   }

   /**
    * Provide double decimal representation of this fraction.
    *
    * @return Decimal (double) representation of this fraction.
    */
   public double getDecimalRepresentation()
   {
      return (double) numerator / denominator;
   }

   /**
    * Provide the BigDecimal representation of this fraction.
    *
    * @return BigDecimal representation of this fraction.
    */
   public BigDecimal getBigDecimalRepresentation()
   {
      final BigDecimal bigNumerator = new BigDecimal(this.numerator);
      final BigDecimal bigDenominator = new BigDecimal(this.denominator);
      return bigNumerator.divide(bigDenominator, this.scale, RoundingMode.HALF_UP);
   }

   /**
    * Provide String representation of this fraction.
    *
    * @return String representation of this fraction.
    */
   public String getStringRepresentation()
   {
      return String.valueOf(this.numerator) + "/" + String.valueOf(this.denominator);
   }

   /**
    * Provide String representation of this fraction.
    *
    * @return String representation of this fraction.
    */
   @Override
   public String toString()
   {
      return getStringRepresentation();
   }

   /**
    * Main function testing this class.'
    *
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      final Fraction fractionOne = new Fraction(2,3);
      System.out.println("2 divided by 3 is " + fractionOne.getDecimalRepresentation());
      System.out.println("2 divided by 3 is " + fractionOne.getBigDecimalRepresentation());
   }
}

In the above example, neither the class nor its methods are declared as final. This allows a renegade subclass to be written as shown in the next class listing for RenegadeFraction.

RenegadeFraction.java

package dustin.examples;

import java.math.BigDecimal;

/**
 * Class extending the 'immutable' Fraction class.
 */
public class RenegadeFraction extends Fraction
{
   /**
    * Parameterized constructor accepting numerator and denominator for the
    * fraction represented by me.
    *
    * @param newNumerator Numerator of fraction.
    * @param newDenominator Denominator of fraction.
    */
   public RenegadeFraction(final int newNumerator, final int newDenominator)
   {
      super(newDenominator, newNumerator);
   }

   /**
    * Provide double decimal representation of this fraction.
    *
    * @return Decimal (double) representation of this fraction.
    */
   public double getDecimalRepresentation()
   {
      return 6.0;
   }

   /**
    * Provide the BigDecimal representation of this fraction.
    *
    * @return BigDecimal representation of this fraction.
    */
   public BigDecimal getBigDecimalRepresentation()
   {
      return new BigDecimal(5.0);
   }

   /**
    * Provide String representation of me.
    *
    * @return My String representation.
    */
   @Override
   public String toString()
   {
      return
        "Fraction with numerator " + getNumerator() + " and denominator "
      + getDenominator();
   }
}

Because the parent Fraction class is not final and its method are not final, the RenegadeFraction class is able to override its methods and return nonsensical results. Note that even if we made the main methods final, the constructor of RenegadeFraction could still intentionally or accidentally swap the constructor arguments passed to its parent class.

This example also shows a misbehaving toString() implementation in the subclass. If we extend the Fraction class and mark the methods we don't want overridden with final, there could be confusing issues surrounding "other" methods like toString(). Do we want to make toString() final or allow the child class to potentially misrepresent its (and its parent's) real contents?

The next code listing demonstrates how a child class can misrepresent its parents to clients. The main code here believes it is dealing with Fraction class and may even make some assumptions based on that. The actual instance passed to the client is the RenegadeFraction resulting from intentional abuse or from careless development.

DemonstrationMain.java

package dustin.examples;

import java.math.BigDecimal;
import static java.lang.System.out;

/**
 * Demonstrate how allowing an immutable class to be extended can reduce
 * immutability, at least from the perspective of its behavior.
 */
public class DemonstrationMain
{
   /**
    * Enumeration representing type of fraction to help in readability of output.
    *
    * This differentiates between the original Fraction and the extending class
    * RenegadeFraction.
    */
   public enum FractionType
   {
      FRACTION("Fraction"),
      RENEGADE_FRACTION("Renegade");

      private String stringRepresentation;

      FractionType(final String newStringRepresentation)
      {
         this.stringRepresentation = newStringRepresentation;
      }

      public String getStringRepresentation()
      {
         return this.stringRepresentation;
      }
   }

   /**
    * Accepts immutable Fraction object and prints its String value.
    *
    * @param fraction Fraction whose String value will be printed.
    * @param type Type of fraction (for ease in reading output).
    */
   public static void printFractionToString(final Fraction fraction, final FractionType type)
   {
      out.println("Fraction [" + type.getStringRepresentation() + "] is " + fraction);
   }

   /**
    * Accepts immutable Fraction object and prints its String value.
    *
    * @param fraction Fraction whose String value will be printed.
    * @param type Type of fraction (for ease in reading output).
    */
   public static void printFractionStringRepresentation(
      final Fraction fraction, final FractionType type)
   {
      out.println(
           "Fraction [" + type.getStringRepresentation() + "] is "
         + fraction.getStringRepresentation());
   }

   /**
    * Accepts immutable Fraction object and prints its decimal representation.
    *
    * @param fraction Fraction whose String value will be printed.
    * @param type Type of fraction (for ease in reading output).
    */
   public static void printFractionDecimalValue(final Fraction fraction, final FractionType type)
   {
      out.println(
           "Fraction [" + type.getStringRepresentation() + "] decimal: "
         + fraction.getDecimalRepresentation());
   }

   /**
    * Accepts immutable Fraction object and prints its BigDecimal representation.
    *
    * @param fraction Fraction whose String value will be printed.
    * @param type Type of fraction (for ease in reading output).
    */
   public static void printFractionBigDecimalValue(final Fraction fraction, final FractionType type)
   {
      out.println(
           "Fraction [" + type.getStringRepresentation() + "] BigDecimal: "
         + fraction.getBigDecimalRepresentation());
   }

   /**
    * Print quotient resulting from division of provided dividend by provided
    * divisor.
    *
    * @param dividend Dividend in division.
    * @param divisor Divisor in division.
    */
   public static void printExternalDivisionResults(
      final BigDecimal dividend, final BigDecimal divisor)
   {
      out.println(
           "Division of dividend " + dividend + " by divisor " + divisor
         + " leads to quotient of " + dividend.divide(divisor));
   }

   /**
    * Main function for executing immutable object or child of immutable object.
    *
    * @param arguments Command-line arguments; none expected;
    */
   public static void main(final String[] arguments)
   {
      final Fraction fraction = new Fraction(2,3);
      final RenegadeFraction renegadeFraction = new RenegadeFraction(2,3);

      printFractionToString(fraction, FractionType.FRACTION);
      printFractionToString(renegadeFraction, FractionType.RENEGADE_FRACTION);

      printFractionStringRepresentation(fraction, FractionType.FRACTION);
      printFractionStringRepresentation(renegadeFraction, FractionType.RENEGADE_FRACTION);

      printFractionDecimalValue(fraction, FractionType.FRACTION);
      printFractionDecimalValue(renegadeFraction, FractionType.RENEGADE_FRACTION);

      printFractionBigDecimalValue(fraction, FractionType.FRACTION);
      printFractionBigDecimalValue(fraction, FractionType.RENEGADE_FRACTION);

      printExternalDivisionResults(
         new RenegadeBigDecimal(fraction.getNumerator()),
         new RenegadeBigDecimal(fraction.getDenominator()));
      printExternalDivisionResults(
         new RenegadeBigDecimal(renegadeFraction.getNumerator()),
         new RenegadeBigDecimal(renegadeFraction.getDenominator()));
   }
}

When the above demonstration class is executed, its output appears as shown below:

1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more