MiguelMunoz
MiguelMunoz

Reputation: 4932

How do I specify an ActionListener in Kotlin?

I want to add an ActionListener to a JButton in Kotlin. In Java, I would just write this:

JPanel makeButtonPanel() {
  JPanel panel = new JPanel(new FlowLayout());
  JButton dirButton = new JButton("Change directory");
  dirButton.addActionListener(e -> chooseDirectory());
  panel.add(dirButton)
  return panel;
}

But it's not so simple in Kotlin. I first tried this:

private fun makeButtonPanel() : JPanel {
  val panel = JPanel(FlowLayout())
  val dirButton = JButton("Choose")
  dirButton.addActionListener(e -> chooseDirectory()) // error message here
  // ...
}

private fun chooseDirectory() { ... }

But I'm getting this error message:

Type Mismatch
Required: ((ActionEvent!) -> Unit)!
Found: KFunction1<ActionEvent, Unit>

I understand that the ! means that this is a java method with uncertain nullability, but that doesn't help me understand how to write it. All I want it to do is call the chooseDirectory() method. There must be a clean, simple way to do this, but I don't see it.

Upvotes: 0

Views: 958

Answers (2)

gidds
gidds

Reputation: 18577

As you've discovered, you need to use braces ({ }).

This is because braces are a necessary part of defining a lambda in Kotlin.  (That differs from languages like Java and Scala, where the necessary part is the -> or => arrow.  That's because in Kotlin the arrow is optional if there are one or no parameters; if one, the it keyword is used.)

Without the braces, the code would call your chooseDirectory() function, and try to pass its result to addActionListener() — which obviously wouldn't work.

Braces are also sufficient: they're taken as defining a lambda unless you're giving the body of a function or method or an if/when branch.  (Again, this differs from most C/Java-like languages.  In Kotlin, if you just want a block scope, you have to use a construct such as run.)

As for the parentheses, they're optional here.  You could include them if you wanted:

dirButton.addActionListener({ chooseDirectory() })

But Kotlin has a convention that if a function's last parameter is a function, you can pass it after the parens:

dirButton.addActionListener(){ chooseDirectory() }

And if that would make the parens empty, then you can omit them entirely:

dirButton.addActionListener{ chooseDirectory() }

That's to allow functions that look like new language syntax.  For example, you may have met the with function:

with(someObject) {
    itsProperty = someValue
}

That's just a perfectly ordinary function, defined in the standard library, and taking a function as its last parameter.  Similarly, repeat:

repeat(10) {
    // Some code to be run 10 times…
}

There's one further thing worth mentioning here.  In Kotlin, lambdas are one way to define functions, which are first-class types and can be defined, passed around, and used just like other types.  This differs from Java, which has traditionally used interfaces for those purposes — often interfaces with a Single Abstract Method (‘SAM interfaces’) — and in which lambdas are little more than syntactic sugar for defining an anonymous implementation of such an interface.

As a special case, for interoperability, Kotlin allows a lambda to define an implementation of a Java SAM interface (or, since Kotlin 1.4, of a Kotlin fun interface), instead of a function.

ActionListener is a Java SAM interface, which is why you can use a lambda here.

Upvotes: 2

MiguelMunoz
MiguelMunoz

Reputation: 4932

Okay, I figured it out, and it was pretty simple. I just have to dispense with the parentheses and say

dirButton.addActionListener { chooseDirectory() }

I'm still not clear on when I should use braces instead of parentheses.

Upvotes: 0

Related Questions