Java Solaris Communities Sun Store Join SDN My Profile Why Join?
 
Bug Database
Bug Detail
Quick Lists
Top 25 Bugs
Top 25 RFE's
Recently Closed Bugs
Printable Page Printable Page


Bug Database
Bug ID: 6373386
Votes 3
Synopsis RFE: Method chaining for instance methods that return void
Category java:specification
Reported Against
Release Fixed
State 11-Closed, duplicate of 6479372, request for enhancement
Priority: 4-Low
Related Bugs 6479372
Submit Date 17-JAN-2006
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) {
		// ...
	}
}
Posted Date : 2006-01-17 19:51:20.0
Work Around
N/A
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.
Posted Date : 2006-11-22 13:21:41.0
Comments
  
  Include a link with my name & email   

Submitted On 07-FEB-2006
arumad
Posible Work Around:
public class BeanA<T extends BeanA<T>> {
	public static void main(String[] args) {
		new BeanC<BeanC>()
				.setA1(0x11)	//BeanB<BeanC<BeanC>>, invisible c-properties
				.setB1(0x21)	//BeanC<BeanC>>
				.setC1(0x31);	//BeanC
		new BeanC<BeanC>()
				.setC1(0x31)	//BeanC
				.setB1(0x21)	//BeanB, c-propepties is not visible
				.setA1(0x31);	//BeanA, (b,c)-propepties is not invisible
	}

	private int a1;

	public int getA1() {
		return a1;
	}
	public T setA1(int a) {
		this.a1 = a;
		return (T) this;
	}
}

class BeanB<T extends BeanB>
		extends BeanA<BeanB<T>> {
	private int b1;

	public int getB1() {
		return b1;
	}
	public T setB1(int b) {
		this.b1 = b;
		return (T) this;
	}
}

class BeanC<T extends BeanC> extends BeanB<BeanC<T>> {
	private int c1;
	private int c2;

	public int getC1() {
		return c1;
	}
	public T setC1(int b) {
		this.c1 = b;
		return (T) this;
	}
}


Submitted On 07-FEB-2006
arumad
I think "fluent language" (method chaining) is workaround for missing feature "cascade expressions".
IMHO, something like this looks much better:
new BeanC().(
	setA1(0x1),
	setB1(0x2),
	setC1(0x3)
)


Submitted On 31-MAY-2007
matthias.ernst
I've made a patch to the javac compiler that implements this request. See http://mail.openjdk.java.net/pipermail/compiler-dev/2007-May/000021.html


Submitted On 02-AUG-2008
UlfZi
> We are not going to change void methods to return the this object.

IMHO no void method must be changed to implement this feature.
Only compiler must insert the 'dup' byte codes.


Submitted On 02-AUG-2008
UlfZi
What about to distinguish chainable from unchainable methods by dropping the 'void' keyword for chainables? So there would be no need to think about a new type, which is not generally usable e.g. for parameters.
OK, there would be some chance for confusion with constructors, but method names normally shouldn't start with a capital letter, but constructors should.



PLEASE NOTE: JDK6 is formerly known as Project Mustang