LoginSignup
3
2

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

Posted at

はじめに

こういうエラーのことです。

'関数名(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ではこのように使えます。

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
引数の部分に波線が引かれて、'()'ではダメだよと。
いやいや引数がないじゃないの!何を入れるっていうの?
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 funCompletableFutureに変換します。

  1. TにKotlin関数の戻り値の型を指定する。
  2. アロー関数の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の関数を作成

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 {
            // エラー処理
        }
    });

例2) 戻り値が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)'

修正例はこちらです。

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ファイルの変更点
./build.gradle
plugins {
    id 'com.android.application' version '8.2.2' apply false
+   id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
}
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'
}

app/build.gradleにライブラリを追加します。

dependencies {
+     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.8.0"
}

Kotlinで、GlobalScope.future()suspend funCompletableFutureに変換するラッパー関数を作成します。

  1. TにKotlin関数の戻り値の型を指定する。
  2. CoroutineScope内にKotlin関数を実施する。
fun ラッパ関数名(必要な引数): CompletableFuture<T> = GlobalScope.future {
    KotlinLibrary().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側でラッパー関数をどう扱うかですが…

例1) そのまま利用する

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

例2) 戻り値が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の共存が可能で、早めに環境を整えて損はないと思います。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2