Java's Immutable Objects

An object is considered immutable if its state cannot change after it is constructed

一、Creating an Immutable Object
1. Don’t provide “setter” methods

If you are building an immutable object its internal state will never change. Task of a setter method is to change the internal value of a field, so you can’t add it.

2. Make all fields final and private.

A private field is not visible from outside the class so no manual changes can’t be applied to it.

Declaring a field final will guarantee that if it references a primitive value the value will never change, if it reference an object the reference can’t be changed. This is not enough to ensure that an object with only private final fields is not mutable. Here is an example showing an object with a private final field and an example on how to mutate its internal state:

public class DateContainer {
  private final Date date;
  public DateContainer() {
      this.date = new Date();
  }
  public Date getDate() {
    return date;
  }
}
....
  DateContainer dateContainer = new DateContainer();
  System.out.println(dateContainer.getDate());
  dateContainer.getDate().setTime(dateContainer.getDate().getTime() + 1000);
  System.out.println(dateContainer.getDate());
  // Now dateContainer date is 1 second after
3. If a field is a mutable object create defensive copies of it for getter methods

We have seen before that defining a field final and private is not enough because it is possible to change its internal state. To solve this problem we need to create a defensive copy of that field and return that field every time it is requested.

Here is the previous class with that modification:

public class DateContainer {
  private final Date date;
  public DateContainer() {
      this.date = new Date();
  }
  public Date getDate() {
    return new Date(date.getTime());
  }
}
....
  DateContainer dateContainer = new DateContainer();
  System.out.println(dateContainer.getDate());
  dateContainer.getDate().setTime(dateContainer.getDate().getTime() + 1000);
  System.out.println(dateContainer.getDate());
  // Now dateContainer date is not changed because we changed the copy, 
  // not the original date
4. If a mutable object passed to the constructor must be assigned to a field create a defensive copy of it

The same problem happens if you hold a reference passed to the constructor because it is possible to change it.

Here we show a modified example of DateContainer that accept a Date for the constructor and we will see how it is possible to change its internal state:

public class DateContainer {
  private final Date date;
  public DateContainer(Date date) {
      this.date = date;
  }
  public Date getDate() {
    return new Date(date.getTime());
  }
}
....
  Date date = new Date();
  DateContainer dateContainer = new DateContainer(date);
  System.out.println(dateContainer.getDate());
  date.setTime(date.getTime() + 1000);
  System.out.println(dateContainer.getDate());
  // Now dateContainer date is 1 second after also if the getter method
  // create a defensive copy of date. We changed the reference passed to the
  // constructor, not the copy.

So holding a reference to an object passed to the constructor can create mutable objects. To solve this problem it is necessary to create a defensive copy of the parameter if they are mutable objects:

public class DateContainer {
  private final Date date;
  public DateContainer(Date date) {
      this.date = new Date(date.getTime());
  }
  public Date getDate() {
    return new Date(date.getTime());
  }
}
....
  Date date = new Date();
  DateContainer dateContainer = new DateContainer(date);
  System.out.println(dateContainer.getDate());
  date.setTime(date.getTime() + 1000);
  System.out.println(dateContainer.getDate());
  // Now dateContainer date is not changed. We create a copy on the constructor
  // so a change to the external date will not affect the internal state of
  // DateContainer instance

Note that if a field is a reference to an immutable object is not necessary to create defensive copies of it in the constructor and in the getter methods it is enough to define the field as final and private. As an example of common immutable objects there are String, all primitive wrappers (Integer, Long, Double, Byte…), BigDecimal, BigInteger.

5. Don’t allow subclasses to override methods

If a subclass override a method it can return the original value of a mutable field instead of a defensive copy of it.

To solve this problem it is possible to do one of the following:

  • Declare the immutable class as final so it can’t be extended

  • Declare all methods of the immutable class final so they can’t be overriden

  • Create a private constructor and a factory to create instances of the immutable class because a class with private constructors can’t be extended

二、Key Points
  1. Don’t provide “setter” methods — methods that modify fields or objects referred to by fields.
  2. Make all fields final and private.
  3. Don’t allow subclasses to override methods. The simplest way to do this is to declare the class as final. A more sophisticated approach is to make the constructor private and construct instances in factory methods.
  4. If the instance fields include references to mutable objects, don’t allow those objects to be changed:
    • Don’t provide methods that modify the mutable objects.
    • Don’t share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.
三、Advantages
  1. An immutable object remains in exactly one state, the state in which it was created. Therefore, immutable object is thread-safe so there is no synchronization issue. They cannot be corrupted by multiple threads accessing them concurrently. This is far and away the easiest approach to achieving thread safety.
  2. Immutable classes are easier to design, implement, and use than mutable classes.
  3. Immutable objects are good Map keys and Set elements, since these typically do not change once created.
  4. Immutability makes it easier to write, use and reason about the code (class invariant is established once and then unchanged).
  5. Immutability makes it easier to parallelize program as there are no conflicts among objects.
  6. The internal state of program will be consistent even if you have exceptions.
  7. References to immutable objects can be cached as they are not going to change. (i.e. in Hashing it provide fast operations).
四、Disadvantages

Creating an immutable class seems at first to provide an elegant solution. However, whenever you do need a modified object of that new type you must suffer the overhead of a new object creation, as well as potentially causing more frequent garbage collections. The only real disadvantage of immutable classes is that they require a separate object for each distinct value.
Programmers are often reluctant to employ immutable objects, because they worry about the cost of creating a new object as opposed to updating an object in place. The impact of object creation is often overestimated, and can be offset by some of the efficiencies associated with immutable objects. These include decreased overhead due to garbage collection, and the elimination of code needed to protect mutable objects from corruption.

参考:
《Immutable Objects in Java》
《A Strategy for Defining Immutable Objects》
Advantages and disadvantages immutable objects in Java》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值