3
votes

Main Question: How can I override kotlin method in java which returns Unit type?

I'm trying to use kotlin library in java and there is a method which name is override fun invoke(): Unit that I have to implement.

but, java compiler keep telling me return type void is not compatible with Unit.

I tried public Unit invoke() { return Unit.INSTANCE; } in java but occurs compile error.

invoke()' in 'myproject' clashes with 'invoke()' in 'library'; 
attempting to use incompatible return type

kotlin interface (in library)

interface MessageHandler<M : Message> : (Message) -> Unit {

  override fun invoke(message: Message): Unit =
    if (messageType.isAssignableFrom(message.javaClass)) {
      println("invoked ${message.javaClass.simpleName}")
    } else {
      throw IllegalArgumentException("Unsupported message type ${message.javaClass.simpleName}")
    }

}

java abstract class (in my project)

public abstract class AbstractMessageHandler<T extends Message> implements MessageHandler<T> {

    @Override
    public void invoke(@NotNull Message message) {
        //this is where the compile error occurs.
    }
}

error message

(Message) in AbstractMessageHandler cannot implement invoke(P1) in Function1
    public void invoke(@NotNull Message message) {
                ^
  return type void is not compatible with Unit
  where P1,R are type-variables:
    P1 extends Object declared in interface Function1
    R extends Object declared in interface Function1
2
Can you show a minimal reproducible example? I used void as the return type and there is no error. - Sweeper
@Sweeper, I added the example. thanks! - Junyoung Oh

2 Answers

2
votes

Looks like a 4-year old unresolved bug: https://youtrack.jetbrains.com/issue/KT-15964

Quoted:

I don't think this is possible now because of compatibility with older binaries. We'll see what we can do, thank you for the report.

An ugly workaround would be to use the nullable type Unit?:

interface TestA<out T> {
    fun foo(): T
}

abstract class TestB : TestA<Unit?> {
    override fun foo(): Unit? {
        // ...
        return Unit
    }
}

With this, you won't be required to override foo in a Java subclass. If you want to override it though, the signature in the Java subclass should be public Unit foo().

If you don't control the library, I'm not sure there's much you can do.

0
votes
  1. If you control the Kotlin library you can :
  • Inherit from java.util.function.Consumer:

    interface MessageHandler<M : Message> : Consumer<Message> {
    
         override fun accept(message: Message): Unit =
             if (messageType.isAssignableFrom(message.javaClass)) {
                 println("invoked ${message.javaClass.simpleName}")
             } else {
                 throw IllegalArgumentException("Unsupported message type ${message.javaClass.simpleName}")
             }
    
    }
    
  • Or use Any instead of Unit and return null in your Java code:

      interface MessageHandler<M : Message> : (Message) -> Any {
    
      override fun accept(message: Message): Any =
          if (messageType.isAssignableFrom(message.javaClass)) {
              println("invoked ${message.javaClass.simpleName}")
          } else {
              throw IllegalArgumentException("Unsupported message type ${message.javaClass.simpleName}")
          }
    
    }
    
  1. If you can't, I think it's better, and if possible, to write AbstractMessageHandler and all its sub classes in Kotlin:

    abstract class AbstractMessageHandler : MessageHandler<Message> {
       override fun invoke(message: Message) {
        // ...
       }
    }
    
  • But and if you won't call the default MessageHandler.invoke and your code won't relate on MessageHandler type, you can simply inherit fromjava.util.function.Consumer:

      public  class AbstractMessageHandler implements Consumer<Message> {
         @Override
         public void accept(Message message) {
            // ...
         }
      }
    

Just to mention that before the error:

(Message) in AbstractMessageHandler cannot implement invoke(P1) in Function1
...

You should see something like:

'invoke(Message)' in '...MessageHandler' clashes with 'invoke(P1)' in 
'kotlin.jvm.functions.Function1'; attempting to use incompatible return type

Because the Java compiler see two interfaces, one from (Message) -> Unit:

public interface kotlin.jvm.functions.Function1 {
   Object invoke(Messsage var1);
}

and the other from MessageHandler:

public interface MessageHandler extends Function1 {
   void invoke(@NotNull Message var1);
}

// (Some details were ommitted for simplicity)

And this is not possible in Java, as MessageHandler.invoke try to override Function1.invoke with different return type, as stated in jls-8.4.8.3,

If a method declaration d1 with return type R1 overrides or hides the declaration of another method d2 with return type R2, then d1 must be return-type-substitutable (ยง8.4.5) for d2, or a compile-time error occurs.