LoginSignup
1
2

More than 5 years have passed since last update.

Kotlinでデフォルト引数を持つ関数のinterfaceをJavaから使うときの嫌なこと

Last updated at Posted at 2017-10-25

Kotlinでデフォルト引数を持つ関数をinterfaceで定義して使うことってよくあると思うのですが、これをJavaから使おうとしたときにちょっと嫌な感じになってしまいました。ベストな方法が知りたいんですがたどり着けなかったので、誰か教えてくれる人が現れることを期待しつつ残しておきます。

なお、インターフェースの実装内容などは検証コードなので適当です。

  • インターフェースの定義(Kotlin)

データベースへの接続を管理するconnect関数をローンパターンで定義。

ConnectionManager
interface ConnectionManager {

  fun createConnection(host: String): Connection {
    return Connection(host)
  }

  fun<A> connect(c: Connection = createConnection("localhost"), f: (Connection) -> A): A {
    try {
      c.begin()
      return f(c)
    } catch (e: Exception) {
      c.rollback()
      throw e
    } finally {
      c.commit()
    }
  }
}
  • インターフェースを利用(Kotlin)

データベースに接続し、select文を発行して、Userインスタンスを返すイメージ

UserDao
class UserDao : ConnectionManager {
  fun findById(id: Int): User {
    return connect { c -> c.select("select * from user where id = ${id}", User()) }
  }
}

ここまでは特に何の問題もないです。で、このインターフェースをJavaからも使いたいと思った時に問題が発生しました。

  • インターフェースを利用(Java) (コンパイルエラー)
UserDaoJava
public class UserDaoJava implements ConnectionManager {
  public User findById(int id) {
    return connect(c -> c.select("select * from user where id = " + id, new User()) );
  }
}

これはコンパイルエラーになります。Javaからはデフォルト引数を省略した引数が一つのconnect関数として呼ぶことができません。

仕方がないので、引数が一つの関数をインターフェースに追加しました。

ConnectionManager
interface ConnectionManager {

 ・・・省略・・・

  // この関数を仕方なく追加
  fun<A> connect(f: (Connection) -> A): A {
    return connect(createConnection("localhost"), f)
  }
}

これで大丈夫かと思いきや、これでもコンパイルが通りません。JavaからはKotlinのインターフェースのデフォルト実装がそのままでは呼べません。そのため、利用するJavaクラス側でオーバーライドしないといけません。

UserDaoJava
public class UserDaoJava implements ConnectionManager {

  ・・・省略・・・

  // 下の3つが必要
  @NotNull
  @Override
  public Connection createConnection(@NotNull String host) {
    // 内部実装はConnectionManagerのものを利用したいのでそのまま呼ぶ。
    return ConnectionManager.DefaultImpls.createConnection(this, host);
  }

  @Override
  public <A> A connect(@NotNull Connection c, @NotNull Function1<? super Connection, ? extends A> f) {
    return ConnectionManager.DefaultImpls.connect(this, c, f);
  }

  @Override
  public <A> A connect(@NotNull Function1<? super Connection, ? extends A> f) {
    return ConnectionManager.DefaultImpls.connect(this, f);
  }
}

これでとりあえずコンパイルも通り動くようになりました。ただ、Kotlin側でConnectionManagerを使うときにconnect関数が2つ見えてしまうのも嫌だし、何よりJava側で利用するたびにオーバーライドするなんてありえない。

なので、Java用のConnectionManagerを作ってしまおう!!

まず、ConnectionManagerからは先ほど追加した引数が一つのconnect関数を削除。そして、新しく作成するJava用のConnectionManagerの方で引数が一つのconnect関数を定義します。

ConnectionManagerJava
public interface ConnectionManagerJava extends ConnectionManager {
  @NotNull
  @Override
  default Connection createConnection(@NotNull String host) {
    return ConnectionManager.DefaultImpls.createConnection(this, host);
  }

  @Override
  default <A> A connect(@NotNull Connection c, @NotNull Function1<? super Connection, ? extends A> f) {
    return ConnectionManager.DefaultImpls.connect(this, c, f);
  }

  default <A> A connect(@NotNull Function1<? super Connection, ? extends A> f) {
    return connect(createConnection("localhost"), f);
  }
}

そして、UserDaoJavaはConnectionManagerJavaを使うように変更

UserDaoJava
public class UserDaoJava implements ConnectionManagerJava {
  public User findById(int id) {
    return connect(c -> c.select("select * from user where id = " + id, new User()) );
  }
}

これで、KotlinのConnectionManagerに追加した引数1つのconnect関数は削除でき、Java側もすっきりしました。ただし、interfaceでdefault実装を使っているので、Java8じゃないといけないという問題がありますが。

こんな感じで、Kotlinのデフォルト引数を持つinterfaceをJavaから使うために、Java側にもinterfaceを作るという荒技?にたどり着いちゃったんですが、何か間違ってる気もする。。。

これで正解なのか、Kotlinのinterfaceで実装しているところから違うのか、それとももっと別のやり方が用意されているのか、気になるところです。

1
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
1
2