`
d02540315
  • 浏览: 32293 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

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:

The output shown above demonstrates how a subclass can misrepresent its parent’s advertised behavior. This can happen anytime implementation inheritance is involved, but it is more insidious when we have reason to believe the class involved is immutable. We could mark the methods as final to reduce much of this, but the constructor still has issues. Furthermore, marking all the parent class’s methods as final rather than simply marking the entire class as final has some drawbacks. These include the potential conundrum about how to handle methods (such as toString()) that really should not be final and the risk of new methods being added to the parent class without the developer remembering or knowing to mark them as final.

In the DemonstrationMain class, I also made use of a custom class called RenegadeBigDecimal. The purpose of this class is to demonstrate the abuse of a subclass of BigDecimal. The code listing for RenegadeBigDecimal is shown next.

RenegadeBigDecimal.java
package dustin.examples;

import java.math.BigDecimal;

/** * Demonstrating the troubles with non-final 'immutable' classes. */

public class RenegadeBigDecimal extends BigDecimal{   
/**    * Parameterized constructor accepting Long for instantiation of BigDecimal.    *    *
 @param val Long parameter intended, but not actually used, for    *    instantiation of BigDecimal.    */   
public RenegadeBigDecimal(final Long val)   {      
    super(-1);   
}

   /**    * Example of intentionally abusing behavior likely expected for an    * immutable class by the subclass.    *    * 
@param divisor Divisor in division process.    */   
public BigDecimal divide(final BigDecimal divisor)   {     
    return this;   
}
}


This last example shows how easy it is to abuse BigDecimal. Because the divide method was overridden to return itself, it is obvious that neither the BigDecimal class nor its method divide were designated as final. Even if the divide method would have been made final, the constructor could still have implemented bad intent and any client code thinking it was a BigDecimal would pay the consequences.

Trade-offs and Drawbacks of final Immutable Classes

There may be times when the ability to override a “largely immutable” or “mostly immutable” class is more important than having a “completely immutable” or “strongly immutable” class. Software design and development is full of trade-offs and this is just another one of those. The benefits of different degrees of mutability can be weighed against the costs. I believe it is no coincidence that Joshua Bloch changed the title of his Effective Java item on immutable objects from “Favor Immutability” in Edition 1 to “Minimize Mutability” in Edition 2. I think this is most readily done by starting with a class that is completely or strongly immutable and only relaxing its mutability in different ways when justified by the benefits of doing so.

Again, at least in my mind, it is no coincidence that the creators of the recently announced Noop project have stated that they wish to encourage immutability and that they wish to discourage implementation inheritance. I think in many of the cases in which implementation inheritance might be desired, it might be better to simply use composition to share common, immutable data and behaviors between two highly related, possibly immutable, classes. That advice doesn’t sound particularly different than what many others have suggested before in terms of all classes, including mutable classes.

Conclusion

The safest way to make a class “strongly immutable” is to designate the entire class as final. Doing so ensures that no mistakes will be made in creating new methods in the future in the immutable class that are not marked final and can be carelessly or maliciously overridden. The final keyword applied at class level also prevents subclasses from “masquerading” as their immutable parent with very different behavior than that expected for the “immutable” parent class.


Source URI: http://www.phphosts.org/2009/09/is-a-java-immutable-class-always-final/
分享到:
评论

相关推荐

    java面向对象之final修饰符.docx

    public final class ImmutableClass { private final String value; public ImmutableClass(String value) { this.value = value; } // 不允许创建子类 } ``` 总结,final关键字在Java中用于保证数据的不可...

    详解Java编程中final,finalize,finally的区别

    public final class ImmutableClass { final int value; public ImmutableClass(int value) { this.value = value; } // final 方法 public final void doSomething() { // ... } } ``` 接下来是`...

    javafinal和static总结.docx

    System.out.println("This is a final method."); } } public class DerivedClass extends FinalExample { // 下面的代码会报错,因为不能重写final方法 // public void method() { // System.out.println(...

    Java中static、this、super、final用法.doc

    final class Immutable { private final int value; Immutable(int value) { this.value = value; } // value 不能被修改 } ``` 总结起来,`static`、`this`、`super`和`final`是Java中控制对象生命...

    java-leetcode题解之Range Sum Query - Immutable.java

    java java_leetcode题解之Range Sum Query - Immutable.java

    java-immutable-collections:Java不可变的基于数组的集合

    ImmutableCollections-基于数组的Java不可变集合 版权所有(c)2017 Nicholas Cull 有关许可信息,请参阅LICENSE.txt。 Java 1.8的基于数组的不可变集合 排序和未排序的变体 风格类似于番石榴收集包装 空友好 Java 8...

    java英文笔试

    What is immutable? **Answer**: An object is considered immutable if its state cannot be changed after it is created. In Java, the `String` class is a classic example of an immutable class. Once a `...

    stati、thi、supe、final关键字详解

    final class ImmutableClass { // 不可继承的类 private int value; ImmutableClass(int value) { this.value = value; } } ``` 总结,static、this、super和final是Java编程中非常重要的关键字。static用于...

    关键字Final

    在Java编程语言中,`final`关键字扮演着非常重要的角色,它有多种用途,用于创建不可改变的对象、变量和类。下面将详细讲解`final`关键字的五种主要使用方式。 1. **常量(Final Variables)** `final`关键字可以...

    前端开源库-immutable-core

    **一、什么是Immutable?** “Immutable”在编程中意味着一旦创建的数据对象就不能被修改。这种特性在JavaScript这样的动态语言中尤为重要,因为它可以防止意外的数据篡改,提高代码的预测性和安全性。Immutable.js...

    Java 9 with JShell

    At present, Gaston is an independent IT consultant and a freelance author who is always looking for new adventures around the world. He was a senior contributing editor at Dr. Dobb's and has written...

    Google中的Guava源码

    Guava is a set of core Java libraries from Google that includes new collection types (such as multimap and multiset), immutable collections, a graph library, and utilities for concurrency, I/O, ...

    is-immutable

    是不可变的 从公开isImmutable函数,但不包含Immutable库的其余部分。 用法 import isImmutable from 'is-immutable' ; isImmutable ( someObj ) ; // => boolean

    Docker and Immutable Infrastructure

    1.Overview of Docker the company and growth 2.Overview of Docker the latest stuff (DCUS announcements) & CaaS;...4.Docker and Immutable infrastructure/Microservices working together.

    Immutable详解及React中实践.pdf

    "Immutable 详解及 React 中实践" Immutable 是一种编程概念,指的是一旦创建,就不能再被更改的数据。它可以解决共享的可变状态问题,提高应用的性能和可维护性。本文将对 Immutable 的概念、实现原理、常见库和在...

    有关于JAVA的一些PPT

    Java中的不可变类型(Immutable Types) 不可变类型是那些一旦创建后就不能改变其状态的对象。例如,使用`final`关键字声明的变量。 ### 8. 垃圾收集(Garbage Collector) Java拥有自动垃圾收集器,它负责回收...

    ImmutableObjects

    ### Immutable Objects in Java 在Java编程语言中,不可变对象(Immutable Objects)是一个重要的概念,尤其是在构建健壮、易于维护的应用程序时。本篇将基于提供的文件内容来深入探讨不可变对象的概念及其在Java中...

    Java搞懂的六个问题.txt

    在Java中,`String` 类型是不可变的(immutable),这意味着一旦一个 `String` 对象被创建后,它的值就不能被修改。例如,在文档中提到的 `"Hello"` 和 `"world!"` 的连接操作实际上创建了一个新的 `String` 对象,...

    java-immutable-collections:Java的高效ImmutablePersistent集合

    Java的不可变集合 概述 Java库的不可变集合(JImmutable Collections)是一组高性能的不可变集合,用于替换或补充标准的java.util集合。 为每个最常用的集合提供功能替换: Java类 JImmutable接口 工厂方法 数组...

Global site tag (gtag.js) - Google Analytics