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


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

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

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

データベースへの接続を管理する`connect関数`をローンパターンで定義。
 
```kotlin: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インスタンスを返すイメージ

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

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

* インターフェースを利用(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関数として呼ぶことができません。

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

```kotlin:ConnectionManager
interface ConnectionManager {

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

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


```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関数を定義します。

```java: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を使うように変更

```java: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で実装しているところから違うのか、それとももっと別のやり方が用意されているのか、気になるところです。