Java Lambdas, SAMs And Events |
Written by Ian Elliot | ||||
Tuesday, 14 November 2017 | ||||
Page 2 of 3
Using a Lambda to Create an Event handlerConsider a JButton object in Swing. This has an addActionListener method that can be used to add an event handler. In most other languages the event handler would be a function but in Java it has to be an object. In this case the object is a SAM - the ActionListener Interface with a single actionPerformed(ActionEvent e) method. We can use a lambda to add an event handler directly:
The lambda is inferred to be of type ActionListener. Unfortunately in Swing not many event handlers are SAMs. Instead interfaces tend to gather together event handlers for sets of related events making it impossible to use a lambda directly. In Android and JavaFX things are a bit better as many events are handled by SAMs. In Android any object that can generate an event will have an addeventListener method which can be used to attach an onEventListener object to the event handler list. The name of the method and the object includes the name of the event. So there is a
method which takes an
object as its only parameter. The OnEventListener objects are all derived from an interface of the same name which defines a single abstract method which acts as the event handler. For example, OnClickListener is an interface which has the single method
defined. To use it as an event handler you have to create an instance that implements onClick and then add it to the event handler list using setOnClickListener. The good news is that as the OnEventListener is a SAM – a Single Abstract Method – we can use a lambda to create an instance and avoid a lot of round about coding. For example to add a click event handler to a button we can use a lambda to create the event handler:
and add it to the button:
This two stage process makes clear what is happening and what role the lambda is playing but it isn’t the way the code would usually be written. In nearly all cases it is simpler to write the lambda within the setOnClickListener call:
This makes it look as if the lambda is just a function that is called when the click event occurs and this isn’t a bad way to think about it when you can forget about the SAMs that make it actually work. So using a lambda we can create event handlers very easily. All we have to do is use the setOnEventListener method and write a lambda as its first and only parameter to act as the event handler. The only complication is to make sure that we get the number of parameters correct in the head of the lambda and treat them correctly in the body. For example, while you don’t have to say that view is a View object in the head of the lambda you still have to know what type it is in the body of the lambda. In JavaFX every event has a setOnEvent method that can be used to add an event handler to an object's list. The event handler class is defined using generics EventHandler<Event> and the event handler function is defined as a method handle(Event evn). You can see that this is very similar to the Android way of doing the job but using generics. Hence most JavaFX events are SAMs. For example, to create a action handler using a lambda
Notice that the target type of the lambda is determined by the type of the parameter of the setOnAction method i.e. an
object - lambdas can take on the type of a generic type as long as the type is fully defined at compile time. When you can use a lambda it is so much more compact than the alternative that it makes it easier to understand the code. ClosureClosure is one of those topics that sounds as if it is going to be difficult. If Java lambdas are not like other lambdas Java's closures are probably not what other languages regard as a closure. Lambda expressions have access to all of the variables that belong to the method that they are declared in. What is more a lambda cannot declare a variable that already exists in the enclosing method – that is a lambda cannot create a shadow variable for a variable that it can already access. For example consider the Android example given earlier:
This looks as if it should just work because all that we have done is to change the previous example so that the view parameter is cast a Button and stored in a new variable b. However if you try this out you will discover that the compiler complains that you cannot declare variable b because it already exists. The lambda has access to the b set in the enclosing method and it cannot create a shadowing variable of the same name. The fact that the lambda has access to the variables in the enclosing method has the strange consequence that you could write the event handler as:
This may look odd but it works. If you don’t think that it is odd then you haven’t noticed that the event handler, the lambda, will be executed when the Button is clicked and this is likely to be well after the enclosing method has finished and all its variables not longer exist – and yet the lambda can still use b to access the Button. The system will keep the value of b so that the lambda can make use of it. This is the essence of a closure – the preserving of variables that have gone out of scope so that a lambda can still access them. Now we come to a subtle point that you might want to skip until you are sure you understand closure or see an error message relating to “final”. The Java closure isn’t as general as you will find in other languages. It captures the values of the variables just once at the time that the lambda is created. To make sure that strange things cannot happen as a consequence of this you can only use variables with are final or effectively final in the lambda. A final variable is one that can be initialized and then is guaranteed not to change. That is a final variable is one that is set to a value just once. You can use the final modifier to make it clear that a variable satisfies this condition and the compiler will check that it is true. So for example you could write the previous event handler as:
The final modifier would cause the compiler to check that b was initialized just once. In fact you don’t need the final modifier because using b in the lambda causes the compiler to check that it is effectively final. It is quite clear the b is only initialized once in the code listed above and so it seems that it is self evidently final but things are more subtle. Suppose the enclosing method was called a second time? Surely this will change the value in b and hence it isn’t final. As b is a local variable it is a new instance of b that is initialized and a new instance of the lambda is created. As the original b isn’t reinitialized it is still final. It can be very difficult sometimes to work out if a variable is final or not but the compiler will do it for you. |
||||
Last Updated ( Tuesday, 14 November 2017 ) |