JavaからKotlinの引数なしの関数を呼んだら引数が足りないと怒られた(suspend fun, Continuation, coroutine)

## はじめに

こういうエラーのことです。
```
'関数名(kotlin.coroutines.Continuation? super 戻り値の型>)' in 'ファイル名' cannot be applied to '()'
```

具体例というとこうです。
```
'getNumber(kotlin.coroutines.Continuation? super java.lang.Integer>)' in 'com.example.myapplication.KotlinLibrary' cannot be applied to '()'
```

分かる人はもしかしたらタイトルの括弧のキーワードで既に察したかもしれませんが、
なにせKotlinに関する知識が0で、キーワードに辿り着くまで時間がかかったもので、この記事が誰かの手引きになるといいと思います。
また、キーワードが分かってからもつまずきました。最終的に選んだ解決策を共有できればと思います。

## 環境
Android Studio Hedgehog | 2023.1.1 Patch 2

JDK: 11
compileSdk: 34
minSdk: 26
targetSdk: 34

Gradleファイルに追記する各ライブラリのバージョンは特に指定がなく、執筆時点の最新のマイナーバージョンを選びました。

## 再現

Javaだけで作成されたAndroidアプリで、Kotlinのライブラリを導入するという背景を前提とします。

こういうKotlinライブラリがあるとします。

```KotlinLibrary.kt
package com.example.kotlin.library

import kotlinx.coroutines.delay

class KotlinLibrary {
    suspend fun getNumber(): Int {
        delay(1000L)
        return 1
    }
}
```

Kotlinではこのように使えます。
```kotlin
suspend fun getNumber(): Int {
    return KotlinLibrary().getNumber()
}
```

newキーワードをつけて、Javaでも同じように…と思ったらエラーが発生しました。

```CoroutineJava.java
package com.example.myapplication;

import com.example.kotlin.library.KotlinLibrary;

public class CoroutineJava {
    public Integer getNumber() {
        return new KotlinLibrary().getNumber();
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243366/eea88538-4ccc-d4ee-739d-b48640a65b40.png)
引数の部分に波線が引かれて、`'()'`ではダメだよと。
いやいや引数がないじゃないの！何を入れるっていうの？
`kotlin.coroutines.Continuation? super java.lang.Integer`って何？？？
と疑問が尽きませんでした。

## 原因

Java側の実装は間違っています。
そもそも、前提知識が不足しています。

Kotlinの`suspend fun`をなんか修飾子がついているな〜としか思っていなかったが、非同期処理の一環だそうです。

https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=ja

https://qiita.com/duke105/items/b5be074c79c6bed4d560

要するに、Java側がそのままでは使えません。
Kotlinでは引数がなくても、Javaでは引数にContinuation型を指定しないといけません。

## 調査
キーワードが分かって、調査がやりやすくなりました。
次に知りたいことはJavaからどうやったらKotlinの`suspend fun`を実行できるかです。

`kotlin suspend fun from java`とか`kotlin Continuation in java`で検索すると多くの解消法が見つかりました。
（Stack Overflowばかりですみません）

https://stackoverflow.com/questions/52869672/call-kotlin-suspend-function-in-java-class

https://stackoverflow.com/questions/54022921/how-to-call-kotlin-suspending-coroutine-function-from-java-7

https://stackoverflow.com/questions/75552035/how-to-call-kotlin-suspend-function-from-java-project

Kotlin側のコードを修正して〜という系の回答は無視して、記載されている各ライブラリを試しました。
最終的に選んだ解消法を紹介します。

## 解消法(Javaのみ)

https://www.baeldung.com/kotlin/suspend-functions-from-java#2-convert-to-future

こちらの方法を採用しました。
`org.jetbrains.kotlinx:kotlinx-coroutines-jdk8`ライブラリを導入すれば、Javaのみのコードで解消できるのが魅力です。
`CompletableFuture`という見慣れた型に変換できるのもいいと思いました。

`app/build.gradle`にライブラリを追加します。
```diff
dependencies {
+     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.8.0"
}
```

Javaの実装を変更します。

`FutureKt.future()`でKotlinの`suspend fun`を`CompletableFuture`に変換します。
1. `T`にKotlin関数の戻り値の型を指定する。
1. アロー関数の`continuation`をKotlin関数に渡す
```Java
CompletableFuture<T> 変数名 = FutureKt.future(
        GlobalScope.INSTANCE,
        EmptyCoroutineContext.INSTANCE,
        CoroutineStart.DEFAULT,
        (scope, continuation) -> new KotlinLibrary().Kotlin関数名(continuation));
return 変数名;
```

再現の例というと、こう変わります。
```Java
CompletableFuture<Integer> getNumber = FutureKt.future(
        GlobalScope.INSTANCE,
        EmptyCoroutineContext.INSTANCE,
        CoroutineStart.DEFAULT,
        (scope, continuation) -> new KotlinLibrary().getNumber(continuation));
return getNumber;
```

後はJava側で`CompletableFuture`をどう扱うかですが…

例１） 戻り値がCompletableFuture<Integer>の関数を作成
```CoroutineJava.java
package com.example.myapplication;

import com.example.kotlin.library.KotlinLibrary;

import java.util.concurrent.CompletableFuture;

import kotlin.coroutines.EmptyCoroutineContext;
import kotlinx.coroutines.CoroutineStart;
import kotlinx.coroutines.GlobalScope;
import kotlinx.coroutines.future.FutureKt;

public class CoroutineJava {
    public CompletableFuture<Integer> getNumber() {
        CompletableFuture<Integer> getNumber = FutureKt.future(
                GlobalScope.INSTANCE,
                EmptyCoroutineContext.INSTANCE,
                CoroutineStart.DEFAULT,
                (scope, continuation) -> new KotlinLibrary().getNumber(continuation));
        return getNumber;
    }
}
```
```利用側.java
new CoroutineJava().getNumber()
    .whenComplete((result, error) -> {
        if(error == null) {
            // resultを使った処理
        } else {
            // エラー処理
        }
    });
```

例２） 戻り値がIntegerの関数を作成
```CoroutineJava.java
package com.example.myapplication;

import com.example.kotlin.library.KotlinLibrary;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import kotlin.coroutines.EmptyCoroutineContext;
import kotlinx.coroutines.CoroutineStart;
import kotlinx.coroutines.GlobalScope;
import kotlinx.coroutines.future.FutureKt;

public class CoroutineJava {
    public Integer getNumber() {
        Integer result = -1;  // 取得できない場合の初期値
        CompletableFuture<Integer> getNumberCompletableFuture = FutureKt.future(
                GlobalScope.INSTANCE,
                EmptyCoroutineContext.INSTANCE,
                CoroutineStart.DEFAULT,
                (scope, continuation) -> new KotlinLibrary().getNumber(continuation));
        try {
            result = getNumberCompletableFuture.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return result;
    }
}
```
```利用側.java
Integer result = getNumber();
```

### 引数がある場合

呼び出したいKotlin関数に引数が存在する場合、`continuation`は引数の最後に指定します。

`continuation`を指定する前のエラー例はこちらです。
```
'関数名(引数の型, kotlin.coroutines.Continuation? super 戻り値の型>)' in 'ファイル名' cannot be applied to '(引数の型)'
```
```
'numToString(int, kotlin.coroutines.Continuation? super java.lang.String>)' in 'com.example.kotlin.library.KotlinLibrary' cannot be applied to '(java.lang.Integer)'
```

修正例はこちらです。
```kotlin
suspend fun numToString(num: Int): String {
    delay(1000L)
    return num.toString()
}
```
```Java
CompletableFuture<String> numToString = FutureKt.future(
        GlobalScope.INSTANCE,
        EmptyCoroutineContext.INSTANCE,
        CoroutineStart.DEFAULT,
        (scope, continuation) -> new KotlinLibrary().numToString(num, continuation));  // 引数の最後にcontinuationを指定
return numToString;
```

## 解消法(Kotlin導入)

私はJavaのみで解消したかったので、上記の解消法を採用しましたが、
こういった意見もあります。

https://stackoverflow.com/a/71599237

> 試す価値もないわ。KotlinでJavaが分かるようなラッパーを作れば？
> JavaとKotlinは同じプロジェクト内で混在しても問題ない。

と超超超ざっくり意訳。
ごもっともだと思います。

なので、

* 元々JavaとKotlinが混在している環境
* Kotlin導入しても問題ない環境

の選択肢として、この解消法も記載します。

JavaプロジェクトにKotlinを導入する方法はAndroid Developersを参照していただければと思います。

https://developer.android.com/kotlin/add-kotlin?hl=ja

こちらの環境では上記サイトと多少違う方法で導入しました。
解説はしませんが、参考として置いておきます。

<details><summary>build.gradleファイルの変更点</summary>

```diff:./build.gradle
plugins {
    id 'com.android.application' version '8.2.2' apply false
+   id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
}
```

```diff:app/build.gradle
plugins {
    id 'com.android.application'
+   id 'org.jetbrains.kotlin.android'
}

android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
+   kotlinOptions {
+       jvmTarget = '1.8'
+   }
}

dependencies {
    ...
+   implementation 'androidx.core:core-ktx:1.12.0'
}
```
</details>



`app/build.gradle`にライブラリを追加します。
```diff
dependencies {
+     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.8.0"
}
```

Kotlinで、`GlobalScope.future()`で`suspend fun`を`CompletableFuture`に変換するラッパー関数を作成します。
1. `T`にKotlin関数の戻り値の型を指定する。
1. CoroutineScope内にKotlin関数を実施する。
```Kotlin
fun ラッパー関数名(必要な引数): CompletableFuture<T> = GlobalScope.future {
    KotlinLibrary().Kotlin関数名(必要な引数)
}
```

再現の例というと、こう変わります。
```Kotlin
fun getNumber(): CompletableFuture<Int> = GlobalScope.future {
    KotlinLibrary().getNumber()
}
```

こういうファイルを作成しました。
```CoroutineKotlin.kt
package com.example.myapplication

import com.example.kotlin.library.KotlinLibrary;

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.future.future
import java.util.concurrent.CompletableFuture

class CoroutineKotlin {
    fun getNumber(): CompletableFuture<Int> = GlobalScope.future {
        KotlinLibrary().getNumber()
    }
}

```

後はJava側でラッパー関数をどう扱うかですが…

例１） そのまま利用する

```利用側.java
new CoroutineKotlin().getNumber()
    .whenComplete((result, error) -> {
        if(error == null) {
            // resultを使った処理
        } else {
            // エラー処理
        }
    });
```

例２） 戻り値がIntegerの関数を作成
```利用側.java
private Integer getNumber() {
    Integer result = -1;  // 取得できない場合の初期値
    try {
        result = new CoroutineKotlin().getNumber().get();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    return result;
}
```
```利用側.java
Integer result = getNumber();
```

## さいごに
自分はJavaで〜と思っていっても、導入したいライブラリがKotlinで作成されたものだと、対応が必要となる場合があります。
その際にKotlinの知識がないと苦戦します…
あと、原因が分かったとしても、Javaだけでなんとかしようとすると、結構苦しみます。
幸い、AndroidのプロジェクトはJavaとKotlinの共存が可能で、早めに環境を整えて損はないと思います。