Android上的Kotlin Closable和SQLiteDatabase

我在我的项目中使用这个代码

fun getAllData(): List<Data> = writableDatabase.use { db -> db.query(...) } 

它在棒棒糖设备上成功执行,但是在棒棒糖之前,它抛出一个ClassCastException异常

 FATAL EXCEPTION: main java.lang.ClassCastException: android.database.sqlite.SQLiteDatabase cannot be cast to java.io.Closeable 

可能是因为它是Java 1.6,并没有“试用资源”功能,但我不确定。

那么,为什么我有这个例外,如何解决呢?

这个问题与try-with-resources无关,只是编译代码和运行时环境之间不兼容的依赖问题。

问题出在编译时SQLiteDatabase实现了Closeable接口,后来换掉了另外一个没有的接口。 但是代码已经编译好了,不知道这个改变。

当你调用一个函数/方法时,所有的参数都被检查以确保它们是正确的类型,这通常由JVM来处理。 对于内联函数和扩展函数,Kotlin编译器插入一个类型检查以确保它在正确的类型上运行,对于这个特定的内联函数,函数的接收器被定义为Closeable 。 这里是方法签名:

 inline fun <T : Closeable, R> T.use(block: (T) -> R): R { ... } 

因此,编译器如下( 在字节码中 )对writableDatabase进行类型检查:

 CHECKCAST java/io/Closeable 

所以在这一点上, SQLiteDatabase的实现必须永远实现Closeable接口,否则编译后的代码将会失败。 通过交换到旧版本的Android,这不再是真实的,你破坏合同,并导致异常。 编译器不能做任何不同的事情。 在我已经编译完代码之后,我将在任何JVM应用程序中将JAR换成完全不同的实现。 改变Android版本基本上交换所有的JAR。

如果所有版本都有一个close方法( 从Kotlin stdlib use函数复制并修改), 那么可以如何解决这个问题是专门为SQLiteDatabase类编写自己的use函数:

 inline fun <T : SQLiteDatabase, R> T.use(block: (T) -> R): R { var closed = false try { return block(this) } catch (e: Exception) { closed = true try { close() } catch (closeException: Exception) { // eat the closeException as we are already throwing the original cause // and we don't want to mask the real exception } throw e } finally { if (!closed) { close() } } } 

现在,内联类型检查将是CHECKCAST android/database/sqlite/SQLiteDatabase ,它将始终成功。