Reputation: 4932
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
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
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