Java 8 is finally out officially and it brings probably the biggest change to the Java platform ever; lambda’s. Lambda’s are a very powerful technology which will have major influence on the way API’s in Java will be written, but the first critical sounds also are heard already.
Lambda’s in Java have two main aspects; one is the powerful stream API, where all kinds of processing is chained and in that way multicore processing is easily made possible. The other aspect is the replacement of the anonymous inner classes scaffolding, and that is probably the thing people are exited about first, when discovering lambda’s in Java 8. Let’s take a peek using my latest hobby project as an example.
In the previous blog I’ve written about CircularPane; a way to layout nodes in JavaFX in a circle. Below is an example of how some tests in CircularPane look (the green circles are debugging hints):
One of the latest additions is that nodes can be animated into their positions. The example below shows that the left two CircularPanes animate their nodes into place “over the arc”, the right two “from origin”.
Now, this animation is a calculation of a progress (0.0 to 1.0) over time. For example the “from origin” slowly adds the difference between the origin and the end location; at 0.0 all nodes are at the origin, at 1.0 all nodes have the full difference added and are at their end location. For this a Transition is used to handle the calculation of the progress value, CircularPane has precalculated all sorts of layout values, so the actual calculation it is pretty straight forward.
new Transition() { ... @Override protected void interpolate(double progress) { for (AnimationLayoutInfo animationLayoutInfo: animationLayoutInfos) { double lX = animationLayoutInfo.originX + (progress * -animationLayoutInfo.originX) + (animationLayoutInfo.nodeLayoutInfo.x * progress); double lY = animationLayoutInfo.originY + (progress * -animationLayoutInfo.originY) + (animationLayoutInfo.nodeLayoutInfo.y * progress); animationLayoutInfo.node.relocate(lX, lY); } } }.playFromStart();
In the code above the “from origin” animation is hardcoded in the Transition, but I would like to make this dynamic, so multiple animations can be supported. In order to facilitate this, an interface is introduced that will hold the actual interpolation calculation, and that is called instead:
// the interface @FunctionalInterface public interface AnimationInterpolation { public void interpolate(double progress, AnimationLayoutInfo animationLayoutInfo); } // a property for setting an implementation of the interface final private ObjectProperty<AnimationInterpolation> animationInterpolationObjectProperty = new SimpleObjectProperty<AnimationInterpolation>(this, "animationInterpolation", null); new Transition() { ... protected void interpolate(double progress) { for (AnimationLayoutInfo animationLayoutInfo: animationLayoutInfos) { animationInterpolationObjectProperty.get().interpolate(progress, lAnimationLayoutInfo); } } }.playFromStart();
Things are in place to have the animation implemented externally. Prior to Java 8 this would involve creating an anonymous inner class like so:
lCircularPane.setAnimationInterpolation(new CircularPane.AnimationInterpolation() { @Override public void interpolate(double progress, AnimationLayoutInfo animationLayoutInfo) { double lX = animationLayoutInfo.originX + (progress * -animationLayoutInfo.originX) + (animationLayoutInfo.nodeLayoutInfo.x * progress); double lY = animationLayoutInfo.originY + (progress * -animationLayoutInfo.originY) + (animationLayoutInfo.nodeLayoutInfo.y * progress); animationLayoutInfo.node.relocate(lX, lY); } });
Even though this is a lot of scaffolding, it is very clear what is going on: you are implementing an interface defined in CircularPane, and you see exactly which method it defines. Now, if the interface changes for any reason, it is obvious that this code will get compiler errors. But also an IDE can easily find all implementations of an interface, because it is explicit. No magic.
But that is a lot of code just to be explicit. So the equivalent lambda notation does make it a lot more readable. Basically the compiler generates the anonymous inner class. It is a trade off between brevity and completeness, but one probably worth trading. And in the end, knowing how lambda’s work, there is still the notion of that anonymous inner class underneath. The scaffolding is just generated for you.
lCircularPane.setAnimationInterpolation( (progress, animationLayoutInfo) -> { double lX = animationLayoutInfo.originX + (progress * -animationLayoutInfo.originX) + (animationLayoutInfo.nodeLayoutInfo.x * progress); double lY = animationLayoutInfo.originY + (progress * -animationLayoutInfo.originY) + (animationLayoutInfo.nodeLayoutInfo.y * progress); animationLayoutInfo.node.relocate(lX, lY); });
But now we move the calculation into a static method inside CircularPane. This allows us to configure what animation to use by simply setting a method reference:
lCircularPane.setAnimationInterpolation(CircularPane::animateFromTheOrigin); static public void animateFromTheOrigin(double progress, AnimationLayoutInfo animationLayoutInfo) { double lX = animationLayoutInfo.originX + (progress * -animationLayoutInfo.originX) + (animationLayoutInfo.nodeLayoutInfo.x * progress); double lY = animationLayoutInfo.originY + (progress * -animationLayoutInfo.originY) + (animationLayoutInfo.nodeLayoutInfo.y * progress); animationLayoutInfo.node.relocate(lX, lY); }
This is where lambda’s become vague, and IMHO let go of the ideas behind Java. Normally if you move the implementation of an interface from an anonymous class into a formal class, than there is a reference to the implemented interface. This is not the case here; the animateFromTheOrigin method actually implements AnimationInterpolation, but that information is not visible in anyway. It appears as just a method with two parameters, only compiler / EDI errors will show otherwise, and that only after making breaking changes to either the interface or the method. And only if the method is used somewhere! If the method is never used as the interface it implements, no compiler or IDE will be able to figure out that it needs to change when refactoring the interface. I do not like that.
Maybe the static method should explicitly state that it is implementing an interface:
static public void animateFromTheOrigin(double progress, AnimationLayoutInfo animationLayoutInfo) implements AnimationInterpolation { ... }
Or there should be an annotation similar to @Override
@Implements(AnimationInterpolation) static public void animateFromTheOrigin(double progress, AnimationLayoutInfo animationLayoutInfo) { ... }
Of course this implementing keyword or annotation can only be used for single method interfaces, but now you know that this method is not just any static two parameter method…
For JFXtras I have written test classes that use both methods, thus allowing the IDE to correctly refactor.