はじめに
こういうエラーのことです。
'関数名(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ライブラリがあるとします。
package com.example.kotlin.library
import kotlinx.coroutines.delay
class KotlinLibrary {
suspend fun getNumber(): Int {
delay(1000L)
return 1
}
}
Kotlinではこのように使えます。
suspend fun getNumber(): Int {
return KotlinLibrary().getNumber()
}
newキーワードをつけて、Javaでも同じように…と思ったらエラーが発生しました。
package com.example.myapplication;
import com.example.kotlin.library.KotlinLibrary;
public class CoroutineJava {
public Integer getNumber() {
return new KotlinLibrary().getNumber();
}
}
引数の部分に波線が引かれて、'()'
ではダメだよと。
いやいや引数がないじゃないの!何を入れるっていうの?
kotlin.coroutines.Continuation? super java.lang.Integer
って何???
と疑問が尽きませんでした。
原因
Java側の実装は間違っています。
そもそも、前提知識が不足しています。
Kotlinのsuspend fun
をなんか修飾子がついているな〜としか思っていなかったが、非同期処理の一環だそうです。
要するに、Java側がそのままでは使えません。
Kotlinでは引数がなくても、Javaでは引数にContinuation型を指定しないといけません。
調査
キーワードが分かって、調査がやりやすくなりました。
次に知りたいことはJavaからどうやったらKotlinのsuspend fun
を実行できるかです。
kotlin suspend fun from java
とかkotlin Continuation in java
で検索すると多くの解消法が見つかりました。
(Stack Overflowばかりですみません)
Kotlin側のコードを修正して〜という系の回答は無視して、記載されている各ライブラリを試しました。
最終的に選んだ解消法を紹介します。
解消法(Javaのみ)
こちらの方法を採用しました。
org.jetbrains.kotlinx:kotlinx-coroutines-jdk8
ライブラリを導入すれば、Javaのみのコードで解消できるのが魅力です。
CompletableFuture
という見慣れた型に変換できるのもいいと思いました。
app/build.gradle
にライブラリを追加します。
dependencies {
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.8.0"
}
Javaの実装を変更します。
FutureKt.future()
でKotlinのsuspend fun
をCompletableFuture
に変換します。
-
T
にKotlin関数の戻り値の型を指定する。 - アロー関数の
continuation
をKotlin関数に渡す
CompletableFuture<T> 変数名 = FutureKt.future(
GlobalScope.INSTANCE,
EmptyCoroutineContext.INSTANCE,
CoroutineStart.DEFAULT,
(scope, continuation) -> new KotlinLibrary().Kotlin関数名(continuation));
return 変数名;
再現の例というと、こう変わります。
CompletableFuture<Integer> getNumber = FutureKt.future(
GlobalScope.INSTANCE,
EmptyCoroutineContext.INSTANCE,
CoroutineStart.DEFAULT,
(scope, continuation) -> new KotlinLibrary().getNumber(continuation));
return getNumber;
後はJava側でCompletableFuture
をどう扱うかですが…
例1) 戻り値がCompletableFutureの関数を作成
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;
}
}
new CoroutineJava().getNumber()
.whenComplete((result, error) -> {
if(error == null) {
// resultを使った処理
} else {
// エラー処理
}
});
例2) 戻り値がIntegerの関数を作成
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;
}
}
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)'
修正例はこちらです。
suspend fun numToString(num: Int): String {
delay(1000L)
return num.toString()
}
CompletableFuture<String> numToString = FutureKt.future(
GlobalScope.INSTANCE,
EmptyCoroutineContext.INSTANCE,
CoroutineStart.DEFAULT,
(scope, continuation) -> new KotlinLibrary().numToString(num, continuation)); // 引数の最後にcontinuationを指定
return numToString;
解消法(Kotlin導入)
私はJavaのみで解消したかったので、上記の解消法を採用しましたが、
こういった意見もあります。
試す価値もないわ。KotlinでJavaが分かるようなラッパーを作れば?
JavaとKotlinは同じプロジェクト内で混在しても問題ない。
と超超超ざっくり意訳。
ごもっともだと思います。
なので、
- 元々JavaとKotlinが混在している環境
- Kotlin導入しても問題ない環境
の選択肢として、この解消法も記載します。
JavaプロジェクトにKotlinを導入する方法はAndroid Developersを参照していただければと思います。
こちらの環境では上記サイトと多少違う方法で導入しました。
解説はしませんが、参考として置いておきます。
build.gradleファイルの変更点
plugins {
id 'com.android.application' version '8.2.2' apply false
+ id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
}
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'
}
app/build.gradle
にライブラリを追加します。
dependencies {
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.8.0"
}
Kotlinで、GlobalScope.future()
でsuspend fun
をCompletableFuture
に変換するラッパー関数を作成します。
-
T
にKotlin関数の戻り値の型を指定する。 - CoroutineScope内にKotlin関数を実施する。
fun ラッパー関数名(必要な引数): CompletableFuture<T> = GlobalScope.future {
KotlinLibrary().Kotlin関数名(必要な引数)
}
再現の例というと、こう変わります。
fun getNumber(): CompletableFuture<Int> = GlobalScope.future {
KotlinLibrary().getNumber()
}
こういうファイルを作成しました。
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側でラッパー関数をどう扱うかですが…
例1) そのまま利用する
new CoroutineKotlin().getNumber()
.whenComplete((result, error) -> {
if(error == null) {
// resultを使った処理
} else {
// エラー処理
}
});
例2) 戻り値がIntegerの関数を作成
private Integer getNumber() {
Integer result = -1; // 取得できない場合の初期値
try {
result = new CoroutineKotlin().getNumber().get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return result;
}
Integer result = getNumber();
さいごに
自分はJavaで〜と思っていっても、導入したいライブラリがKotlinで作成されたものだと、対応が必要となる場合があります。
その際にKotlinの知識がないと苦戦します…
あと、原因が分かったとしても、Javaだけでなんとかしようとすると、結構苦しみます。
幸い、AndroidのプロジェクトはJavaとKotlinの共存が可能で、早めに環境を整えて損はないと思います。