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

Java theory and practice: To mutate or not to mutate?

阅读更多

Summary:   Immutable objects have a number of properties that make working with them easier, including relaxed synchronization requirements and the freedom to share and cache object references without concern for data corruption. While immutability may not necessarily make sense for all classes, most programs have at least a few classes that would benefit from being immutable. In this month's Java theory and practice, Brian Goetz explains some of the benefits of immutability and some guidelines for constructing immutable classes.

 

 

 

 

 

An immutable object is one whose externally visible state cannot change after it is instantiated. The String , Integer , and BigDecimal classes in the Java class library are examples of immutable objects -- they represent a single value that cannot change over the lifetime of the object.

Benefits of immutability

Immutable classes, when used properly, can greatly simplify programming. They can only be in one state, so as long as they are properly constructed, they can never get into an inconsistent state. You can freely share and cache references to immutable objects without having to copy or clone them; you can cache their fields or the results of their methods without worrying about the values becoming stale or inconsistent with the rest of the object's state. Immutable classes generally make the best map keys. And they are inherently thread-safe, so you don't have to synchronize access to them across threads.

Freedom to cache

Because there is no danger of immutable objects changing their value, you can freely cache references to them and be confident that the reference will refer to the same value later. Similarly, because their properties cannot change, you can cache their fields and the results of their methods.

If an object is mutable, you have to exercise some care when storing a reference to it. Consider the code in Listing 1, which queues two tasks for execution by a scheduler. The intent is that the first task would start now and the second task would start in one day.


Listing 1. A potential problem with a mutable Date object

  Date d = new Date();
  Scheduler.scheduleTask(task1, d);
  d.setTime(d.getTime() + ONE_DAY);
  scheduler.scheduleTask(task2, d);

 

Because Date is mutable, the scheduleTask method must be careful to defensively copy the date parameter (perhaps through clone() ) into its internal data structure. Otherwise, task1 and task2 might both execute tomorrow, which is not what was desired. Worse, the internal data structure used by the task scheduler could become corrupt. It is very easy to forget to defensively copy the date parameter when writing a method like scheduleTask() . If you do forget, you've created a subtle bug that's not going to show up for a while, and one that will take a long time to track down when it does. An immutable Date class would have made this sort of bug impossible.

Inherent thread safety

Most thread-safety issues arise when multiple threads are trying to modify an object's state concurrently (write-write conflicts) or when one thread is trying to access an object's state while another thread is modifying it (read-write conflicts.) To prevent such conflicts, you must synchronize access to shared objects so that other threads cannot access them while they are in an inconsistent state. This can be difficult to do correctly, requires significant documentation to ensure that the program is extended correctly, and may have negative performance consequences as well. As long as immutable objects are constructed properly (which means not letting the object reference escape from the constructor), they are exempt from the requirement to synchronize access, becuase their state cannot be changed and therefore cannot have write-write or read-write conflicts.

The freedom to share references to immutable objects across threads without synchronization can greatly simplify the process of writing concurrent programs and reduces the number of potential concurrency errors a program could have.

Safe in the presence of ill-behaved code

Methods that take objects as arguments should not mutate the state of those objects, unless they are explicitly documented to do so or are effectively assuming ownership of that object. When we pass an object to an ordinary method, we generally don't expect that the object will come back changed. However, with mutable objects, this is simply an act of faith. If we pass a java.awt.Point to a method such as Component.setLocation() , there's nothing stopping setLocation from modifying the location of the Point we pass in or from storing a reference to that point and changing it later in another method. (Of course, Component doesn't do this, because it would be rude, but not all classes are so polite.) Now, the state of our Point has changed without our knowledge, with potentially hazardous results -- we still think the point is in one place, when in fact it is in another. However, if Point were immutable, then such hostile code would not be able to modify our program state in such a confusing and dangerous way.

Good keys

Immutable objects make the best HashMap or HashSet keys. Some mutable objects will change their hashCode() value depending on their state (like the StringHolder example class in Listing 2). If you use such a mutable object as a HashSet key, and then the object changes its state, the HashSet implementation will become confused -- the object will still be present if you enumerate the set, but it may not appear to be present if you query the set with contains() . Needless to say, this could cause some confusing behavior. The code in Listing 2, which demonstrates this, will print "false," "1," and "moo."


Listing 2. Mutable StringHolder class, unsuitable for use as a key

    public class StringHolder {
        private String string;

        public StringHolder(String s) {
            this.string = s;
        }

        public String getString() {
            return string;
        }

        public void setString(String string) {
            this.string = string;
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            else if (o == null || !(o instanceof StringHolder))
                return false;
            else {
                final StringHolder other = (StringHolder) o;
                if (string == null)
                    return (other.string == null);
                else
                    return string.equals(other.string);
            }
        }

        public int hashCode() {
            return (string != null ? string.hashCode() : 0);
        }

        public String toString() {
            return string;
        }

        ...

        StringHolder sh = new StringHolder("blert");
        HashSet h = new HashSet();
        h.add(sh);
        sh.setString("moo");
        System.out.println(h.contains(sh));
        System.out.println(h.size());
        System.out.println(h.iterator().next());
    }

 


When to use immutable classes

Immutable classes are ideal for representing values of abstract data types, such as numbers, enumerated types, or colors. The basic numeric classes in the Java class library, such as Integer , Long , and Float , are immutable, as are other standard numeric types such as BigInteger and BigDecimal . Classes for representing complex numbers or arbitrary-precision rational numbers would be good candidates for immutability. Depending on your application, even abstract types that contain many discrete values -- such as vectors or matrices -- might be good candidates for implementing as immutable classes.

The Flyweight pattern

Immutability is what enables the Flyweight pattern, which uses sharing to facilitate using objects to represent large numbers of fine-grained objects efficiently. For example, you might wish to represent each character of a word-processing document or each pixel in an image with an object, but a naive implementation of such a strategy would be prohibitively expensive in memory usage and memory management overhead. The Flyweight pattern employs a factory method to dispense references to immutable fine-grained objects and uses sharing to keep the object count down by only having a single instance of the object corresponding to the letter "a." For more information on the Flyweight pattern, see the classic book, Design Patterns (Gamma, et. al; see Resources ).

Another good example of immutability in the Java class library is java.awt.Color . While colors are generally represented as an ordered set of numeric values in some color representation (such as RGB, HSB, or CMYK), it makes more sense to think of a color as a distinguished value in a color space, rather than an ordered set of individually addressable values, and therefore it makes sense to implement Color as an immutable class.

Should we represent objects that are containers for multiple primitive values, such as points, vectors, matrices, or RGB colors, with mutable or immutable objects? The answer is. . . it depends. How are they going to be used? Are they being used primarily to represent multi-dimensional values (like the color of a pixel), or simply as containers for a collection of related properties of some other object (like the height and width of a window)? How often are these properties going to be changed? If they are changed, do the individual component values have meaning within the application on their own?

Events are another good example of candidates for implementation with immutable classes. Events are short-lived, and are often consumed in a different thread than they were created in, so making them immutable has more advantages than disadvantages. Most AWT event classes are not implemented as being strictly immutable, but could be with small modifications. Similarly, in a system that uses some form of messaging to communicate between components, making the message objects immutable is probably sensible.


Guidelines for writing immutable classes

Writing immutable classes is easy. A class will be immutable if all of the following are true:

  • All of its fields are final
  • The class is declared final
  • The this reference is not allowed to escape during construction
  • Any fields that contain references to mutable objects, such as arrays, collections, or mutable classes like Date :
    • Are private
    • Are never returned or otherwise exposed to callers
    • Are the only reference to the objects that they reference
    • Do not change the state of the referenced objects after construction

The last group of requirements sounds complicated, but it basically means that if you are going to store a reference to an array or other mutable object, you must ensure that your class has exclusive access to that mutable object (because otherwise someone else could change its state) and that you do not modify its state after construction. This complication is necessary to allow immutable objects to store references to arrays, since the Java language has no way to enforce that elements of final arrays are not modified. Note that if array references or other mutable fields are being initialized from arguments passed to a constructor, you must defensively copy the caller-provided arguments or else you can't be sure that you have exclusive access to the array. Otherwise, the caller could modify the state of the array after the call to the constructor. Listing 3 shows the right way -- and wrong way -- to write a constructor for an immutable object that stores a caller-provided array.


Listing 3. Right and wrong ways to code immutable objects

class ImmutableArrayHolder {

  private final int[] theArray;

  // Right way to write a constructor -- copy the array
  public ImmutableArrayHolder(int[] anArray) {
    this.theArray = (int[]) anArray.clone();
  }

  // Wrong way to write a constructor -- copy the reference
  // The caller could change the array after the call to the constructor
  public ImmutableArrayHolder(int[] anArray) {
    this.theArray = anArray;
  }

  // Right way to write an accessor -- don't expose the array reference
  public int getArrayLength() { return theArray.length }
  public int getArray(int n)  { return theArray[n]; }

  // Right way to write an accessor -- use clone()
  public int[] getArray()       { return (int[]) theArray.clone(); }

  // Wrong way to write an accessor -- expose the array reference
  // A caller could get the array reference and then change the contents
  public int[] getArray()       { return theArray }
}

 

With some additional work, it is possible to write immutable classes that use some non-final fields (for example, the standard implementation of String uses lazy computation of the hashCode value), which may perform better than strictly final classes. If your class represents a value of an abstract type, such as a numeric type or a color, you will want to implement the hashCode() and equals() methods too, so that your object will work well as a key in a HashMap or HashSet . To preserve thread safety, it is important that you not allow the this reference to escape from the constructor.


Infrequently changing data

Some data items remain constant for the lifetime of a program, whereas others change frequently. Constant data are obvious candidates for immutability, and objects with complex and frequently changing states are generally inappropriate candidates for implementation with immutable classes. What about data that changes sometimes, but not often? Is there any way to obtain the convenience and thread-safety benefits of immutability with data that sometimes changes?

The CopyOnWriteArrayList class, from the util.concurrent package, is a good example of how to harness the power of immutability while still permitting occasional modifications. It is ideal for use by classes that support event listeners, such as user interface components. While the list of event listeners can change, it generally changes much less often than events are generated.

CopyOnWriteArrayList behaves much like the ArrayList class, except that when the list is modified, instead of mutating the underlying array, a new array is created and the old array is discarded. This means that when a caller obtains an iterator, which internally holds a reference to the underlying array, the array referenced by the iterator is effectively immutable and therefore can be traversed without synchronization or risk of concurrent modification. This eliminates the need to either clone the list before traversal or synchronize on the list during traversal, both of which are inconvenient, error-prone, and exact a performance penalty. If traversals are much more frequent than insertions or removals, as is often the case in certain types of situations, CopyOnWriteArrayList offers better performance and more convenient access.


Summary

Immutable objects are much easier to work with than mutable objects. They can only be in one state and so are always consistent, they are inherently thread-safe, and they can be shared freely. There are a whole host of easy-to-commit and hard-to-detect programming errors that are entirely eliminated by using immutable objects, such as failure to synchronize access across threads or failing to clone an array or object before storing a reference to it. When writing a class, it is always worthwhile to ask yourself whether this class could be effectively implemented as an immutable class. You might be surprised at how often the answer is yes.

分享到:
评论

相关推荐

    a hill climbing algorithm to mutate a set of ellipses

    A program that performs a hill climbing algorithm to mutate a set of ellipses to best match an input picture.

    Laravel开发-laravel-mutate

    在Laravel框架中,"mutate"通常指的是属性的访问器和 mutator,这是 Laravel 提供的数据模型(Eloquent ORM)中的一个重要特性。通过使用访问器和 mutator,开发者可以自定义模型属性的获取和设置方式,从而实现数据...

    用RoundedImageView将图片裁剪为圆形、椭圆形

    4. `app:riv_mutate_background`:是否改变背景色。设置为`true`时,背景色会根据图像进行透明度处理。 例如,以下XML代码将创建一个圆形的RoundedImageView: ```xml xmlns:app=...

    swr-mutate-many:针对SWR的多个缓存键调用mutate的小功能

    针对SWR的多个缓存键调用mutate的函数很少。 用法 安装它 $ yarn add swr swr-mutate-many 导入并使用 import { mutateMany } from 'swr-mutate-many' ; mutateMany ( 'key-*' , 'New Value' , false ) ; 现在,...

    data-practice:数据分析实践

    这包括数据清洗,如处理缺失值(用`is.na()`检查,`complete.cases()`或`na.omit()`删除),异常值检测,以及数据转换(如使用`scale()`进行标准化,或`dplyr`包中的`mutate()`、`filter()`等函数进行数据操作)。...

    mutate_cpp:C ++突变测试环境

    Mutate ++-C ++突变测试环境是一种通过更改程序源代码(称为“突变”)并检查程序的测试套件是否检测到此突变来检测程序中的错误的技术。 突变被设计为模仿典型的编程错误(例如,一字不漏的错误)。 如果测试套件未...

    Mutate.m

    Mutate.m

    by_row:一个替代rowwise()和mutate()+ map()的建议

    by_row():map()或rowwise()的替代方法 马修·凯3/3/2021 这是一个针对rowwise()或map()的新替代方案的简短建议,用于对mutate中的...## x non-numeric argument to binary operator ## i Input `c` is `a + 2 *

    GA-Chromosome-.zip_Chromosome.java_chromosome

    在给定的压缩包文件中,我们关注的主要内容是与遗传算法(Genetic Algorithm, GA)和染色体(Chromosome)类相关的Java编程。遗传算法是一种启发式搜索方法,模拟了自然选择和遗传的过程来解决优化问题。在这个场景...

    will-mutate::dna:‍:female_sign::dna:运行时测试可检测对象的突变

    特征实时检测任何与代理兼容的突变引发导致突变的代码的堆栈跟踪在对象中包含指向突变的属性路径用作实用程序功能安装npm install will-mutate代码const proxify = require ( "will-mutate/proxify" ) ;function foo...

    bird-tracking::satellite:鸟类追踪-大型鸟类的GPS追踪网络

    参考数据:电子表格→用于查询(定义为dplyr::mutate() )的数据的→可以上载到Movebank的生成的文件 GPS数据: 数据库→通过将选择的标签与研究相关联,实时输入Movebank 加速度数据:尚未定义 数据集 Movebank和...

    创建要添加到octokit核心作为对等方依赖的octokit插件:一个示例mutate-github-repositories-cli,它在我所有的Octokit插件存储库中创建了一个问题

    用法git clone https://github.com/gr2m/create-issue-to-add-octokit-core-as-peer-dependency-to-octokit-plugins.gitcd create-issue-to-add-octokit-core-as-peer-dependency-to-octokit-plugins$ npx mutate-...

    imageSharp示例

    var image = Image.Load("path_to_your_image.jpg", new JpegDecoder()); ``` 接下来,我们讨论如何调整图像的亮度。ImageSharp提供了一个名为`Adjust Brightness`的方法,允许你以百分比增加或减少图像的亮度: `...

    dexcov:并排生成Dextool Mutate与LCOV

    葡聚糖 生成突变(C / C ++突变测试)与 (C / C ++代码覆盖率)并排演示的工具 示例1:原始主题 $ dexcov example/lcov_html example/dextool_html -o example/out $ xdg-open example/out/lz4.c.... 示例2:LCOV主题 ...

    使用遗传 算法在matlab中解决设施选址问题_MATLAB_代码_下载

    遗传算法中的设施位置 本项目使用遗传算法在matlab中解决设施选址问题。 问题和模型在文件'Problem&...% distance_con:hub-to-hub单位距离成本与hub-non-hub单位距离成本的比值 更多详情,请下载后阅读README.md文件

    logstash-filter-java:通过实现Java接口编写logstash过滤器

    配置文件中,使用`filter { java { jar => "/path/to/your/filter.jar" } }`来加载你的Java过滤器。 7. **测试与调试**: 可以通过Logstash的命令行工具或集成到现有日志处理管道中来测试你的过滤器。如果遇到问题,...

    人工智能遗传算法JAVA编程

    根据给定的信息,我们可以深入探讨遗传算法在Java编程中的应用,并特别关注其在人工智能领域的实践。下面将详细解析与遗传算法相关的几个核心概念,并通过具体的Java编程实例来展示遗传算法如何应用于解决实际问题。...

    Java解决旅行商问题.docx

    ### Java解决旅行商问题 #### 一、旅行商问题(TSP)概述 旅行商问题(Traveling Salesman Problem, TSP)是一个经典的组合优化问题,其目标是找到访问一组城市并最终返回出发城市的最短可能路径,同时确保每个...

    rquery:R的数据整理和查询生成运算符。根据GPL-2或GPL-3许可选择进行分发

    这是本着R的base::transform()或dplyr的dplyr::mutate()的精神,并使用了在magrittr在R流行的风格的管道。 运算符本身遵循Codd关系代数中的选择,并增加了传统的SQL “窗口函数”。 有关rquery的背景和上下文的更多...

    Nikto安全扫描工具

    -id+ Host authentication to use, format is id:pass or id:pass:realm -list-plugins List all available plugins -mutate+ Guess additional file names -mutate-options+ Provide extra information for ...

Global site tag (gtag.js) - Google Analytics