Lambda Expressions are a new Java 8 language construct that fosters this pattern of development. An equivalent to Lambda Expressions -- Anonymous Inner Classes -- has existed for years and is used widely in Swing and Java FX development. However, the shortened syntax can make a difficult-to-read block of code (loaded with generics, etc) more understandable.
This post presents a few snippets of code showing the new Lambda Expressions in a JavaFX application.
Lambda Expressions
Lambda Expressions are based on Lambda Calculus which is a rigorous way for mathematicians to express functions. Lambda Calculus has been in computer programming for decades. When I was a college student in the early 90's, several of my courses involved the Scheme programming language which was heralded as the model pure functional programming language (not really the model useful programming language, though).
In Java
The Lambda Expressions themselves don't bring any new functionality to the language such as animated UI controls or threads that will take advantage of multi-core processing. They're a syntactic shortcut, but I suppose that these shortcuts will add up as projects get larger to be significant. Consider the following statement which will disable a ToolBar Button if a Tab is hidden and enabled the ToolBar Button if the Tab is shown.
elTab.setOnSelectionChanged(event -> tbClear.setDisable( !elTab.isSelected() ) );
Without Lambda Expressions, the code would be written with an Anonymous Inner Class.
errorLogTab.setOnSelectionChanged( new EventHandler<Event>() {
@Override
public void handle(Event arg0) {
tbClear.setDisable( !errorLogTab.isSelected() );
}
});
Distilling one line of code from six isn't going to make much of a difference by itself. However, if you have 10 tabs that need this same type of treatment, you'll have 10 lines of code instead of 60. This means that you can easily see the 10 lines in one glance where as the 60 lines might scroll of the page.
More significantly, you don't have to sort through the class definition which involves generics and a compiler directive (@Override). You also save an import in the process.
This is a variation of the EventHandler, but using keys.
This is a variation of the EventHandler, but using keys.
scene.setOnKeyPressed(
keyEvent -> {
KeyCode key = keyEvent.getCode();
if (key == KeyCode.ESCAPE && (sp.getChildren().get(1) == fp)) {
alertController.action();
}
});
Elements of the Lambda Expression
In Java, unlike other programming languages, a Java Interface is needed to provide the compiler with the extra guidance it needs to infer the types of the parameters being passed in. This Interface is referred to as a "Functional Interface" and it's any interface that has only a single method. For example,
public interface Command {
void doSomething(String arg0);
}
Fortunately, there are several Functional Interfaces available in Java that you will want to use in place of your own: Consumer, Predicate. I don't recommend writing your own as that will take away from the standardization of your code.
Another Example
In this example, I am passing in a block of functionality to a confirmation screen. This is not using the synchronous modal technique involving a Stage. Rather, there is an overlay presented which will only accept an Ok or Cancel (or Escape key) that will remove the overlay. In this overlay style, there is no "showAndWait()". My calling method returns and the overlay code takes over. However, the overlay code needs to know what to do when finished.
Here is how this confirmation step is called. This is in a class called MainViewController.java.
alertController.setConfirmationDialog(
"Confirm Update",
"This wil update " + npoms + " POM files. Continue?",
mv -> mv.doUpdate()
);
vbox.toBack();
setConfirmationDialog() sets up a VBox with message, details, and Button controls. (It's based on the Alert template from SceneBuilder.) mv.doUpdate() is a method that will actually perform the business logic. This method is in a class called AlertController.java.
setConfirmationDialog() makes use of the standard Consumer Functional Interface. It appears in the method declaration.
public void setConfirmationDialog(String message, String details, Consumer<MainViewController> okCallback) {
actionButton.setVisible(false);
cancelButton.setVisible(true);
okButton.setVisible(true);
messageLabel.setText( message );
detailsLabel.setText( details );
this.okCallback = okCallback;
}
Finally, the callback (we don't know that it's an update in AlertController) is applied in response to an @FXML action.
WeakReference<MainViewController> mainViewControllerRef;
@FXML
public void ok() {
okCallback.accept(mainViewControllerRef.get());
Parent flowPane = gp.getParent();
flowPane.toBack();
}
Weak Reference Digression
As a side note, I'm using a Weak Reference to hold onto the MainViewController object. That's because the relationship is bi-directional. MainViewController holds a strong reference to the AlertController. I worked on a JavaFX project that was experiencing memory leaks, so I started following this pattern based on some recent work with an iOS app. In the aforementioned project, most of the inter-Controller associates were in one-direction, so I didn't have to do this across-the-board.
I haven't confirmed this, but I suspect that one class is holding a reference to another and that was tying garbage collection up. I'll blog on this later if I find anything more interesting.
Conclusion
In day-to-day coding, the shortened syntax is welcomed. However, I believe that there is going to be a more significant savings when looking at a large project on the whole. 1 line of code from 6 isn't significant, but 10 from 60 or 100 from 600 starts to become more significant. Moreover, Lambda Expressions are being used in other non-Java languages such as Objective-C and Javascript (Dojo). This means that new classes of developers will start "thinking in Lambda" when expressing themselves and implementing requirements.
UPDATE March 1, 2015 - A Predicate Lambda in JavaFX
Here's a Lambda that I'm using in JavaFX to pull out the sole Rectangle in a Group's children. It's using the ObservableList filtered() function which takes a java.util.func.Predicate. Predicate has a test() method.
void inTransit(Group g, Double x, Double y) { logger.debug("[TRANSIT] x=" + x + ", y=" + y); FilteredList<?> fl = g.getChildren().filtered(obj -> obj instanceof Rectangle); Rectangle r = (Rectangle)fl.get(0); r.setX(x); r.setY(y); }
No comments:
Post a Comment