JDK-6373386 : RFE: Method chaining for instance methods that return void
  • Type: Enhancement
  • Component: specification
  • Sub-Component: language
  • Affected Version: 6
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2006-01-17
  • Updated: 2010-04-04
  • Resolved: 2006-11-22
Related Reports
Duplicate :  
Description
A DESCRIPTION OF THE REQUEST :
If method is an instance method and is declared with a return type of void, the type and the result of this method invocation have to be the instance, on which method is called.

JUSTIFICATION :
There is a certain technique, which is called method chaining (aka fluent interface). Here is a sample

// this is a class that exposes fluent interface
public class SampleBean {
	public SampleBean setPropertyA(String value) {
		// ...
		return this;
	}
	public SampleBean performSomeOperation() {
		// ...
		return this;
	}
}

// and implied usage looks like
SampleBean bean =
	new SampleBean()
		.setPropertyA(a)
		.performSomeOperation();

But a problem arises if we attempt to create DerivedBean that extends SampleBean.

// this class extends SampleBean and attempts to expose fluent interface as well
public class DerivedBean extends SampleBean {
	public DerivedBean setPropertyB(String value) {
		// ...
		return this;
	}
}

In this case fluent interface is broken since we cannot write following:

DerivedBean bean =
	new DerivedBean()
		.setPropertyA(a)
		.setPropertyB(b)
		.performSomeOperation();

Method setPropertyA is inherited from SampleBean and declared there with a return type SampleBean, therefore we setPropertyB cannot be invoked after setPropertyA (or another method that is inherited and returns base class).

I see two workarounds for this problem.

1. Derived classes have to override all inherited fluent methods and redefine return type. Like this:
public class DerivedBean extends SampleBean {
	public DerivedBean setPropertyB(String value) {
		// ...
		return this;
	}
	public DerivedBean setPropertyA(String value) {
		super.setPropertyA(value)
		return this;
	}
	public DerivedBean performSomeOperation() {
		super.performSomeOperation();
		return this;
	}
}

It works because covariant returns are supported. Such class as StringBuffer follows this way. Its 'append' methods look like:

public synchronized StringBuffer append(String str) {
// AbstractStringBuilder.append(String),
// which returns 'this' but AbstractStringBuilder
	super.append(str);
	return this;
}

2. Use explicit type cast in method chaining
DerivedBean bean = ((DerivedBean)
	((DerivedBean) new DerivedBean()
		.setPropertyA(a))
		.setPropertyB(b)
		.performSomeOperation());

Such class as Throwable implies this way
try {
	lowLevelOp();
} catch (LowLevelException le) {
// Throwable.initCause(Throwable),
// which returns 'this' but Throwable
	throw (HighLevelException)
		new HighLevelException().initCause(le);
}

As you can see the first way leads to necessity to review and update derived classes when the base class is extended with new fluent method. Also derived classes will bloat out extensively.
The second way is inconvenient. It leads unsafe and hardly readable code, so that benefits of fluent interface are disappeared.

The proposed request for enhancement will allow more easily and safely define and use fluent interface (method chaining).
We would better consider the type of void instance method invocation as the type of instance, on which method is to be invoked; and the result of void instance method invocation as that instance.
Then the stated above example could be rewritten as following:

// this is a class that exposes fluent interface
public class SampleBean {
	// fluent method
	public void setPropertyA(String value) {
		// ...
	}
	// fluent method
	public void performSomeOperation() {
		// ...
	}
}
// this is a class that exposes fluent interface and also inherit fluent methods from its base
public class DerivedBean extends SampleBean {
	// fluent method
	public void setPropertyB(String value) {
		// ...
	}
}

// usage
DerivedBean bean =
	new DerivedBean()
		.setPropertyA(a)
		.setPropertyB(b)
		.performSomeOperation();

And this usage could be compiled to this bytecode:
;;;;;;;
new DerivedBean
dup
invokespecial DerivedBean."<init>":()V
dup	; remember receiver for subsequent call setPropertyB
ldc "a"
invokevirtual DerivedBean.setPropertyA:(Ljava/lang/String;)V;
dup	; remember receiver for subsequent call performSomeOperation
ldc "b"
invokevirtual DerivedBean.setPropertyB:(Ljava/lang/String;)V;
dup	; remember receiver for subsequent 'astore bean'
invokevirtual DerivedBean.performSomeOperation:()V;
astore bean
;;;;;;;

I strongly believe this RFE does not break either language or platform compatibility.
Platform compatibility: there are no changes in JVM and libraries
Language compatibility: void values were not allowed to be used before

Alternative solution
In order to distinguish 'fluent' methods from 'authentic void', special syntax might be introduced (which also does not break language compatibility)
// this is a class that exposes fluent interface
public class SampleBean {
	// fluent method
	// return 'this', neither 'void' nor 'SampleBean'
	public this setPropertyA(String value) {
		// ...
	}
	// fluent method
	// return 'this', neither 'void' nor 'SampleBean'
	public this performSomeOperation() {
		// ...
	}
}
// this is a class that exposes fluent interface and also inherit fluent methods from its base
public class DerivedBean extends SampleBean {
	// fluent method
	// return 'this', neither 'void' nor 'DerivedBean'
	public this setPropertyB(String value) {
		// ...
	}
}

Comments
EVALUATION We are not going to change void methods to return the this object. The terminology 'fluent interface' does not suggest it, but this request is really for a ThisClass/ThisType type (c.f. the 'this' type in examples near the end of the Description). Such a type would make method chaining type-safe and easy. Consequently, I am closing this request as a duplicate of 6479372.
09-11-2006