Method chaining is a technique that defines several methods that return the this
reference of the current object. It is a convenience mechanism that allows multiple method invocations on the same object to occur, in a single statement. However, unless special care is taken, the implementation may not be safe for multithreaded useEach method invocation returns this
which is then used to invoke the next method in the chain. Many objects provide setter methods that return this
, enabling the user to chain multiple setter methods together.
Method chaining should not be used in a multi-threaded environment, because a set of chained methods is non-atomic, and it is easy to violate CON07-J. Do not assume that a group of calls to independently atomic methods is atomic.
Noncompliant Code Example
Several design patterns exist Method chaining is useful for building an object and setting its optional fields. However, not all of them provide initialization safety, in thata multi-threaded environment, a thread may observe the object before its construction is overinconsistent values in the object's fields if it allows method chaining. This noncompliant code example shows the unsafe Javabeans pattern.
Code Block | ||
---|---|---|
| ||
final class Currency { // Total amount requested (mandatory fields) private int dollars = -1; // Initialize to default value private int cents = -1; // Initialize to default valueUSCurrency { // Change requested, denomination (optional fields) private int quarters = 0; private int dimes = 0; private int nickels = 0; private int pennies = 0; public CurrencyUSCurrency() {} // no argument constructor // Setter methods public CurrencyUSCurrency setDollarsetQuarters(int amountquantity) { dollarsquarters = amountquantity; return this; } public CurrencyUSCurrency setCentssetDimes(int amountquantity) { centsdimes = amountquantity; return this; } public CurrencyUSCurrency setQuarterssetNickels(int quantity) { quartersnickels = quantity; return this; } public CurrencyUSCurrency setDimessetPennies(int quantity) { dimespennies = quantity; return this; } } // ... // Client code: final USCurrency currency = public Currency setNickels(int quantitynew USCurrency(); new Thread(new Runnable() { public void nickels = quantity;run() { return thiscurrency.setQuarters(1).setDimes(1); } public Currency setPennies(int quantity}).start(); new Thread(new Runnable() { public void pennies = quantity;run() { return thiscurrency.setQuarters(2).setDimes(2); } } // Client code: Currency USD = new Currency(); USD.setDollar(10).setCents(50).setQuarters(42.start(); |
The Javabeans pattern uses a no-argument constructor along with a series of parallel setter methods to build an object. This pattern is not thread-safe and can lead to inconsistent object state. Moreover, it permits another thread to access the object even though it may only be partially initialized (not all required fields are initialized).In this example code, the currency
object might wind up with 1 quarter and 2 dimes!
Compliant Solution
Wiki Markup |
---|
This compliant solution uses the variant of the Builder pattern's \[[Gamma 95|AA. Java References#Gamma 95]\] variantthat is suggested by Bloch \[[Bloch 08|AA. Java References#Bloch 08]\] to ensure thread safety and atomicity of object creation. |
Code Block | ||
---|---|---|
| ||
final class CurrencyUSCurrency { // Total amount requested (mandatory fields) private final int dollars; quarters private final int cents= 0; // Change requested, denomination (optional fields) private final int quarters; dimes private final int dimes= 0; private final int nickels = 0; private final int pennies; = // Static class member 0; private CurrencyUSCurrency(Builder builder) { dollars = builder.dollars; cents = builder.cents; this.quarters = builder.quarters; this.dimes = builder.dimes; this.nickels = builder.nickels; this.pennies = builder.pennies; } // Static class member public static class Builder { private final int dollars; private final int cents; private int quarters = 0; private int dimes = 0; private int nickels = 0; private int pennies = 0; public Builder(int dollars, int cents) { } this.dollars = dollars; // Setter this.cents = cents;methods } public Builder quarterssetQuarters(int quantity) { this.quarters = quantity; return this; } public Builder dimessetDimes(int quantity) { this.dimes = quantity; return this; } public Builder nicklessetNickels(int quantity) { this.nickels = quantity; return this; } public Builder penniessetPennies(int quantity) { this.pennies = quantity; return this; } public CurrencyUSCurrency build() { return new CurrencyUSCurrency(this); } } } // ... // Client code: CurrencyUSCurrency USDcurrency = new CurrencyUSCurrency.Builder(10,50).setQuarters(3).quarterssetDimes(423).build(); |
The idea is to call the constructor with the required parameters and obtain a builder object. Each optional parameter can be set using setters on the builder. The object construction concludes with the invocation of the build()
method. This also has the effect of making the class Currency
immutable; consequently it is also thread-safe.
If input needs to be validated, make sure that the values are copied from the builder class to the containing class's fields prior to checking. The builder class does not violate SCP03-J. Do not expose sensitive private members of the outer class from within a nested class because it maintains a copy of the variables defined in the scope of the containing class. These take precedence and as a result do not break encapsulation.
If the number of fields is small, it is better to synchronize the setter methods instead of using this design pattern. But take care to ensure that the setter methods provide the required degree of atomicity, see CON07-J. Do not assume that a group of calls to independently atomic methods is atomic for more information.
Exceptions
EX1: A class may employ method chaining in a multi-threaded environment as long as it documents this fact. Consequently client code must provide client-side locking in order to preserve the thread-safety of its code.
Code Block | ||
---|---|---|
| ||
// This class is not thread-safe! A client must provide locking on any
// USCurrency object in a multi-threaded environment!
final class USCurrency {
// Change requested, denomination (optional fields)
private int quarters = 0;
private int dimes = 0;
private int nickels = 0;
private int pennies = 0;
public USCurrency() {}
// Setter methods
public USCurrency setQuarters(int quantity) {
quarters = quantity;
return this;
}
public USCurrency setDimes(int quantity) {
dimes = quantity;
return this;
}
public USCurrency setNickels(int quantity) {
nickels = quantity;
return this;
}
public USCurrency setPennies(int quantity) {
pennies = quantity;
return this;
}
}
// ...
// Client code:
final USCurrency currency = new USCurrency();
final Object lock = new Object();
new Thread(new Runnable() {
public void run() {
synchronized (lock) {
currency.setQuarters(1).setDimes(1);
}
}
}).start();
new Thread(new Runnable() {
public void run() {
synchronized (lock) {
currency.setQuarters(2).setDimes(2);
}
}
}).start();
|
This code achieves thread-safety by having the client code perform all modification of the USCurrency
object only while a lock
is held.
Risk Assessment
Using implementations of method chaining that are not thread-safe can lead to non-deterministic behavior.
...