Я использую этот код в своем проекте
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
который всегда будет успешным.