Reputation: 125
I am processing a collection of instances of MyCustomType
as follows:
fun runAll(vararg commands: MyCustomType){
commands.forEach { it.myMethod() }
}
Besides instances of MyCustomType
, I'd like to pass and process lambdas of type () -> Unit
, something like this:
fun runAll(vararg commands: Any){
for(command in commands){
if (command is MyCustomType){
command.myMethod()
} else
if(command is () -> Unit){
command.invoke()
}
}
}
The line if(command is () -> Unit){
does not compile with the following message: Cannot check for instance of erased type: () -> Unit
.
Is there a way to check if an object is () -> Unit
at runtime?
I have seen another answer that recommends using wildcards. I do not think that is relevant: my code does not use generics.
Upvotes: 2
Views: 1630
Reputation: 23252
Is there a reason why your MyCustomType
isn't a Function0
by itself?
If it would be, you could just use a vararg commands : () -> Unit
and you could then just call command()
regardless on what is passed (which is by the way the same as calling command.invoke()
). You can do that even now with your current code:
Change signature for runAll
to:
fun runAll(vararg commands: () -> Unit) { ...
Then call it using:
runAll(MyCustomType()::myMethod, { println("self-built-function") } /*, ... */)
or the same written a bit differently with its own val:
val customObj = MyCustomType()
runAll({ customObj.myMethod() }, { println("self-built-function") })
Moreover, if you require all commands to have a certain return value you can do so still, e.g. just use vararg commands: () -> String
if all your commands must return a String
.
Using Any
in that regard may lead to headaches in the future. If you narrow down your type, you are at least sure that you will always have commands behaving in the same way. Moreover you get the best code completion support that way. With Any
you can pass anything, but it doesn't run
everything.
If you only want to check whether a parameter is a lambda, you can also use the following:
someObj is Function<*>
However you can't call invoke
then without casting it to a specific function type, i.e.: Function0
, Function1
, etc.
Regarding Function0
, etc. Todd already answered that. But as he also mentioned in his warning: there is a downside to that. I can only recommend omitting Any
whenever you can.
Upvotes: 0
Reputation: 31690
Kotlin is going to compile your lambda into an instance of Function0
. If you know this, you can use a when
block to compare and smart cast quite nicely:
fun runAll(vararg commands: Any) {
commands.forEach {
when(it) {
is Function0<*> -> it.invoke()
is MyCustomType -> it.myMethod()
}
}
}
And then to call it:
fun main(args: Array<String>) {
runAll(
MyCustomType(),
{ println("Lambda!") }
)
}
Warning: The downside of this approach is that with type erasure, you don't know if you're getting a Function0<Unit>
or a Function0<Int>
, because the type isn't available at runtime for you to make that determination. That means somebody could give you a lambda that returns something and you would ignore the results.
Upvotes: 2