Kotlin Closable и SQLiteDatabase на Android

Я использую этот код в своем проекте

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

Он успешно выполняется на устройствах Lollipop, но на пре-Lollipop он генерирует ClassCastException

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

Вероятно, потому что это Java 1.6 и не имеет функции try-with-resources, но я не уверен.

Итак, почему у меня есть это исключение и как оно может быть исправлено?

Проблема не имеет ничего общего с try-with-ресурсами и является просто несовместимой проблемой зависимости между скомпилированным кодом и средой выполнения.

Проблема заключается во время компиляции. SQLiteDatabase реализует интерфейс Closeable а затем вы Closeable его на другую реализацию, которая этого не делает. Но код уже скомпилирован и не знает об этом изменении.

Когда вы вызываете функцию / метод, все параметры проверяются, чтобы убедиться, что они являются правильными типами, которые обычно обрабатываются JVM. Для встроенных и расширенных функций компилятор Kotlin вставляет проверку типа, чтобы убедиться, что он работает с правильным типом, и для этой конкретной встроенной функции приемник функции определяется как Closeable . Вот подпись метода:

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

Таким образом, компилятор строит проверку типа в writeableDatabase следующим образом ( в байт-коде ):

 CHECKCAST java/io/Closeable 

Таким образом, на данном этапе реализация SQLiteDatabase должна навсегда реализовать интерфейс Closeable иначе скомпилированный код не будет работать. Перейдя на более старую версию Android, где это уже не так, вы нарушаете контракт и вызывают исключение. Компилятор никак не может сделать что-то другое. Это будет то же самое, что и я, заменяя JAR в любом приложении JVM на один с радикально разными реализациями после того, как я уже скомпилировал свой код. Изменение версий Android в основном свопит все JAR.

Что вы можете сделать, чтобы обойти эту проблему , написать свою собственную функцию use специально для класса SQLiteDatabase если все версии имеют метод close ( скопированный и измененный из функции use Kotlin stdlib ):

 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() } } } 

Теперь inline typecheck будет CHECKCAST android/database/sqlite/SQLiteDatabase который всегда будет успешным.