JDK 7: Part 2: My Working Life Would Be Even More Easier, If Java Had Closures
|
So if this article does not convince you about the simplification of closures and what they bring to the Java language, then I am really not sure what will..
|

Dear Reader
This is the second part of the closure blog entry. A couple of weeks ago I wrote an blog article called
My Working Life Would Be Easier, If Java Had Closures.
The first part introduced the third party library called
Electronic Desktop Trading com.eti.libapi. It was an usual library because it raise a checked exception and mostly mutator calls. I talked about how closures would help to avoid the checked exception try/catch/finally boilerplate.
package com.eti.libapi;
public class IGroup {
public IGroup find( String name ) throws EDTAPIException;
public void setLongName( String value ) throws EDTAPIException;
public void setShortName( String value ) throws EDTAPIException;
public void setCounterpartyID( String value ) throws EDTAPIException;
/*...*/
}
Alex Buckley from Sun Microsystems got in touch with some questions and suggestions. Alex asked about the variable
groupLongName, where did it come? Sorry about that, because
groupLongName is simply a holding variable, which was unfortunately omitted from the first article.
public class BrokerServiceTask {
public void updateGroup( BrokerRequest request, BrokerResponse response) {
// ...
XData ml = request.getXMLBeansDocument().getContact().getContactRecord();
String groupLongName = ml.getFullName();
try {
group.setLongName(groupLongName);
// ...
group.save(); /* !!! */
}
catch (ETDAPIException e) {
response.addError("Unable to process data invalid [" + groupName + "]", e);
}
}
}
Next Alex asked about the BrokerResponse object.
"It seems odd that you pass the BrokerResponse object around rather than the BrokerRequest. The dissonance is highest when you pass a
BrokerResponse to ParamContext's constructor, even though it takes a BrokerRequest. (This is not germane to closures, of course.)"
To be perfectly honest, I was using the command object pattern, which can be seem in the Servlet specification, to abstract away the request and response. The BrokerResponse has the structure:
import java.util.*;
class BrokerResponse {
private String messageText;
private List<BrokerError> errorList = new ArrayList<BrokerError>();
private List<BrokerError> warningList = new ArrayList<BrokerError>();
/* constructor and getter/setter omitted */
public void addError( String message ) {
addError( message, null );
}
public void addError( String message, Throwable t ) {
synchronized (errorList) {
errorList.add(error);
}
}
public void clearErrorList() {
synchronized (errorList) {
errorList.clear();
}
}
public int getErrorSize(){
synchronized (errorList) {
return errorList.size();
}
}
public addWarning( String message ) { /*... */ }
public addWarning( String message, Throwable t ) { /*... */ }
}
It depends on a BrokerError object, which looks like this
class BrokerError {
private String message;
private int errorCode;
private String errorDesc;
private Throwable exception;
/* constructor and getter/setter ommitted */
}
I hope this makes it clearer, and explains the reason why the param context receives a BrokerResponse object. The inspiration comes from Java Servlets API 1.0, which allows a response to be defined in the HTTP worker that renders content. The same design form-factor is very usual for components operating on requests coming from to message oriented bus. Designers typically enforce the
separation of concerns. This object encapsulates everything to do with the request to the service and that object encapsulates everything to do the response. It is true, however, that in some realisations of command design pattern, inside the industry, that the response object has tenacious link to the request object.
Here is the original code for the
AbstractETDFieldSetter from the first article.
// AbstractEDTFieldSetter.java
import com.eti.libapi.*;
public class AbstractETDFieldSetter
{
private IGroup group;
private BrokerResponse response;
public AbstractETDFieldSetter(IGroup group, BrokerResponse response)
{
super();
this.group = group;
this.response = response;
}
// We need Closures in Java
public abstract doExecute(String value) throws Exception ;
public void execute(String value)
{
try {
doExecute(value);
}
catch (Exception e) {
response.addError("Unable to set field", e);
}
}
}
Alex correctly notes that if the IGroup and the XData are made final, then we do not need pass in these parameters to a ParamContext object at all. In other words a parameter context object is not necessary.
"ParamContext was never really necessary because the bodies of doExecute in your ContextBasedFieldSetter objects could have referred to group and ml, if only they were final:"
He is correct again. This is a solution around the problem, if your outside scoped variable are invariant from the point of view of the anonymous inner classes. You can define them as final. Therefore, we can rewrite the BrokerServiceTask as follows:
import com.eti.libapi.*;
public class BrokerServiceTask {
public void updateGroup( BrokerRequest request, BrokerResponse response) {
final IGroup = group api.getGroupManager.find(request.getGroupName());
final XData ml = request.getXMLBeansDocument().getContact().getContactRecord();
//...
FinalContextFieldSetterExecutor.execute( response,
new FinalContextBasedFieldSetter{
public void doExecute() {
group.setExternalReferenceID(
StringUtils.notNullAndTrim(ml.getExternalRefID()));
}
});
// ...
}
}
Of course your mileage may vary, read as you will need create a context object, if the executors do directly change the input parameters. This will be really be apparent when your executor needs to return data.
The obvious implementation of the
FinalContextBasedFieldSetter and
FinalContextFieldSetterExecutor are the following classes:
class FinalContextFieldSetterExecutor {
public static void execute( BrokerResponse response, FinalContextBasedFieldSetter executor ) {
try {
executor.doExecute();
}
catch (Exception e) {
response.addError("Unable to set field", e);
}
}
}
class abstract FinalContextBasedFieldSetter {
public abstract void doExecute();
}
Finally, Alex found a big hole in my closures example. This is exactly what you get if you theorise about writing the code without putting it through a real life working compiler in order to verify your programming syntax and sense.
As you indicate, a closure replaces a ContextBasedFieldSetter and your sanitisedSetter method replaces the ContextFieldSetterExecutor class.
A closure's function type is certainly a better description of "a setter" than ContextBasedFieldSetter.doExecute(ParamContext)!
However, your closures-as-setters aren't capturing anything in the enclosing scope. They need to be passed IGroup and XData objects when
> they're invoked. That is, in sanitisedSetter, you can't say block.invoke() if block expects two arguments. What you mean to write is this, which indeed captures group and ml from the enclosing scope:
sanitisedSetter( response, { =>
group.setShortName(StringUtils.notNullAndTrim(ml.getShortName()));
});
The code is above is exactly what I want to write. The parameters
group and
ml do not have to be final, because a closure, by definition, captures the lexically scope of the variable that it is defined in. Yours truly admits this was an oversight. Closures in the Groovy, Ruby and Lisp language operate the same way after all.
Here is a slightly changed library function sanitisedSetter() that abstracts away the
boilerplate exception handling. We need to make the sanitisedSetter to be exception transient, hence we throw a generic exception. The call is still a static method of BrokerServiceTask class.
public class BrokerServiceTask {
/*...*/
public static void sanitisedSetter( BrokerRequest request,
{ => void throws X extends ETDAPIException } block )
{
try {
block.invoke();
}
catch (EDTAPIException e) {
response.addError("Unable to set field", e);
}
}
}
If this article does not convince you about the simplification of closures, what they bring to the existing Java language, as it now stands, then I am really not sure what will.
This is Peter Pilgrim. Out!