|
Quick Lists
|
|
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
|
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
|
|
|
 |