I think it's common to define a function with default arguments in Kotlin and use it, but when I tried to use it from Java, I felt a little unpleasant. I'd like to know the best way, but I couldn't find it, so I'll leave it in the hope that someone will tell me.
The implementation contents of the interface are appropriate because they are verification codes.
Define a connect function
that manages the connection to the database with a loan pattern.
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()
}
}
}
Image that connects to the database, issues a select statement, and returns a User instance
UserDao
class UserDao : ConnectionManager {
fun findById(id: Int): User {
return connect { c -> c.select("select * from user where id = ${id}", User()) }
}
}
There is no particular problem so far. So, I had a problem when I wanted to use this interface from Java as well.
UserDaoJava
public class UserDaoJava implements ConnectionManager {
public User findById(int id) {
return connect(c -> c.select("select * from user where id = " + id, new User()) );
}
}
This will result in a compilation error. From Java, arguments that omit the default arguments cannot be called as one connect function.
I couldn't help it, so I added a function with one argument to the interface.
ConnectionManager
interface ConnectionManager {
···abridgement···
//Add this function unavoidably
fun<A> connect(f: (Connection) -> A): A {
return connect(createConnection("localhost"), f)
}
}
I wonder if this is okay, but it still doesn't compile. From Java, the default implementation of Kotlin's interface cannot be called as is. Therefore, it must be overridden on the Java class side to be used.
UserDaoJava
public class UserDaoJava implements ConnectionManager {
···abridgement···
//I need the bottom three
@NotNull
@Override
public Connection createConnection(@NotNull String host) {
//I want to use the internal implementation of ConnectionManager, so I call it as it is.
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);
}
}
Now the compilation works as well. However, I don't like seeing two connect functions when using ConnectionManager on Kotlin side, and above all, I can't override it every time I use it on Java side.
So let's create a ConnectionManager for Java !!
First, delete the connect function with one argument added earlier from ConnectionManager. Then, define the connect function with one argument in the newly created ConnectionManager for 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);
}
}
And UserDaoJava changed to use 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()) );
}
}
Now you can remove the one-argument connect function you added to Kotlin's ConnectionManager, and the Java side is clean. However, since the interface uses the default implementation, there is a problem that it must be Java 8.
With this kind of feeling, I came up with the trick of creating an interface on the Java side in order to use the interface with Kotlin's default arguments from Java, but I feel that something is wrong. .. ..
I'm wondering if this is the correct answer, if it's different from what Kotlin's interface implements, or if there's a different way.
Recommended Posts