34

I'm developing a JavaFX8 application in Scala but I couldn't figure out how to pass a method reference to an event handler. To clarify, I'm not using ScalaFX library but build my application directly on top of JavaFX.

Here's the related code snippet.

InputController.java (I wrote this test class in Java to isolate the issue to consume a method reference only)

public class InputController {
    public void handleFileSelection(ActionEvent actionEvent){
        //event handling code
    }

    public InputController() {
        //init controller
    }
}

This works (Java)

InputController inputController = new InputController();
fileButton.setOnAction(inputController::handleFileSelection);

This doesn't work (Scala)

val inputController = new InputController
fileButton.setOnAction(inputController::handleFileSelection)

Here's the error message from the compiler (Scala 2.11.6).

Error:(125, 45) missing arguments for method handleFileSelection in class Main;
follow this method with '_' if you want to treat it as a partially applied function
    fileButton.setOnAction(inputController::handleFileSelection)
                                            ^

If I use Scala 2.12.0-M2 instead, I get a different error message.

Error:(125, 45) missing argument list for method handleFileSelection in class Main
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `handleFileSelection _` or `handleFileSelection(_)` instead of `handleFileSelection`.
    fileButton.setOnAction(inputController::handleFileSelection)
                                            ^

Is there a native way which Scala can leverage method references introduced in Java 8? I'm aware of the implicit conversions approach to use a lambda expression but I want to know if there is a way to use a method reference similar to Java 8 without needing to use the lambda decleration.

Mustafa
  • 2,447
  • 23
  • 31
  • Note: Java doesn't have method references. You can have an object which has a method which calls another method (which is what lambdas actually do) – Peter Lawrey Aug 10 '15 at 19:28
  • I think I'm confused with your comment "Java doesn't have method references". I thought what I'm trying to do is no different than the ComparisonProvider example in the official documentation (link: https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html). – Mustafa Aug 10 '15 at 19:40
  • This question is basically a duplicate of http://stackoverflow.com/questions/24369449/ in short, you could create an implicit conversion from (T) => Unit to EventHandler[T] and import that implicit. – Angelo Genovese Aug 11 '15 at 15:16
  • @angelogenovese I'm aware of the question, however, I was wondering if it is possible to use a method reference avoiding the lambda syntax all together. I have edited my question to reflect this. – Mustafa Aug 11 '15 at 16:07
  • ScalaFX is an open source project. There are reasons preventing you from using it, but there is no for looking inside the code to get usage examples – ayvango Aug 12 '15 at 17:53

4 Answers4

26

inputController::handleFileSelection is Java syntax, which isn't supported or needed in Scala because it already had a short syntax for lambdas like this: inputController.handleFileSelection _ or inputController.handleFileSelection(_) (inputController.handleFileSelection can also work, depending on the context).

However, in Java you can use lambdas and method references when any SAM (single abstract method) interface is expected, and EventHandler is just such an interface. In Scala before version 2.11 this isn't allowed at all, in 2.11 there is experimental support for using lambdas with SAM interfaces, which has to be enabled using -Xexperimental scalac flag, and starting from 2.12 it is fully supported and doesn't need to be enabled.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • 1
    When I enable the experimental features, `fileButton.setOnAction(inputController.handleFileSelection)` compiles with no errors. However, instead of being passed as a reference `handleFileSelection` method is executed immediately when this line is executed. I guess that's why it is still an experimental feature. – Mustafa Aug 11 '15 at 14:58
  • what worked for me is to enable experimental features and let the lambda expression handle method reference as the following. `fileButton.setOnAction((event: ActionEvent) => inputController.handleFileSelection)`. – Mustafa Aug 11 '15 at 16:03
  • Do you mean `(event: ActionEvent) => inputController.handleFileSelection(event)`? The version in your comment really shouldn't work. – Alexey Romanov Aug 11 '15 at 18:19
  • Or is it possible that your `handleFileSelection` method actually _doesn't_ take an `ActionEvent`? That would explain how it _could_ be executed immediately without an actual `ActionEvent` as described in your first comment. – Alexey Romanov Aug 11 '15 at 18:22
  • my mistake, it was indeed `(event: ActionEvent) => inputController.handleFileSelection(event)`. – Mustafa Aug 11 '15 at 18:28
  • 1
    In that case `inputController.handleFileSelection(_)` or at worst `inputController.handleFileSelection(_: ActionEvent)` should work as well. – Alexey Romanov Aug 12 '15 at 09:03
  • inputController.handleFileSelection(_) works! Thanks! Could you add this to the answer if possible? – Mustafa Aug 12 '15 at 13:56
  • I'd expect `inputController.handleFileSelection _`, already in the answer, to work also. Does it? – Alexey Romanov Aug 12 '15 at 14:30
  • Unfortunately, `inputController.handleFileSelection _` does not work. – Mustafa Aug 12 '15 at 15:18
  • There are multiple types of Java method references. The cool thing is that they allow static typed method reflections, which unfortunately isn't possible with Scala. For example, in Java this is possible: `printMethodInfo(Product::getName); // Output: class=Product, property=name`. Whether it is "needed" in Scala, well, I need it at the moment. – Devabc Jan 20 '16 at 15:01
  • @Devabc It's possible, but in a different way: https://github.com/dwickern/scala-nameof. – Alexey Romanov Dec 20 '16 at 11:26
4

You should pass function which applying one parameter of type ActionEvent:

val button = new Button()
val inputController = new InputController()

def handler(h: (ActionEvent => Unit)): EventHandler[ActionEvent] =
  new EventHandler[ActionEvent] {
    override def handle(event: ActionEvent): Unit = h(event)
  }

button.setOnAction(handler(inputController.handleFileSelection))
Sergii Lagutin
  • 10,561
  • 1
  • 34
  • 43
  • Unfortunately, this doesn't work. `found : Unit required: javafx.event.EventHandler[javafx.event.ActionEvent]` I can make it work by changing the method signature to `public EventHandler handleFileSelection(){` but that defies the whole purpose of using a method reference. – Mustafa Aug 10 '15 at 19:45
  • @Mustafa why didn't you try just pass `inputController`? It looks like an implementation of target interface, doesn't it? – Sergii Lagutin Aug 10 '15 at 19:47
  • @Mustafa if you wanna use method reference we should talk about some function (consumer, supplier etc.) What does `fileButton.setOnAction` expect? – Sergii Lagutin Aug 10 '15 at 19:52
  • inputController will be handling events from multiple UI elements and I would like to handle them separately. – Mustafa Aug 10 '15 at 19:52
  • here https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ButtonBase.html#setOnAction-javafx.event.EventHandler- – Mustafa Aug 10 '15 at 19:55
  • @Mustafa thanks. I tried to repeat your code. I suppose that scala functions and java method reference use different algorithm of type inference so all my hints don't work. I add to answer only working code I found but I think you were waiting for better answer. – Sergii Lagutin Aug 10 '15 at 20:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/86641/discussion-between-mustafa-and-sergey-lagutin). – Mustafa Aug 10 '15 at 20:24
1

If you want a method reference that also takes the class instance as a parameter, for instance like String::length in Java, you can do (_:String).length which is equivalent to (s:String) => s.length().

The types of these are in Java Function<String, Integer> and in Scala thus String => Int.

WASDi
  • 161
  • 2
  • 3
0

Could you try

fileButton.setOnAction(() => inputController.handleFileSelection())
Sang Yun Park
  • 459
  • 7
  • 14