Итак, я переписываю какой-то устаревший код приложения android.
Часть этого изменения включает в себя представление моделей представлений. И часть , которая включает в себя изменение класса UserManager
, который раньше был object
, на AndroidViewModel
.
class UserManager(application: Application) : AndroidViewModel(application) {
private val userData: MutableMap<User, MutableMap<String, Any>> = object : HashMap<User, MutableMap<String, Any>>() {
override fun get(key: User): MutableMap<String, Any>? {
val former = super.get(key)
val current = former ?: mutableMapOf()
if (current !== former) this.put(key, current)
return current
}
}
init {
restoreActiveUsers()
}
override fun onCleared() {
persistActiveUsersData()
}
private fun restoreActiveUsers() {
val decodedUsers: List<User> = ... load users from persistent storage ...
decodedUsers.forEach { userData[it] } //create an entry in [userData] with the user as key, if none exists
...
}
}
Блок init
является новым, поскольку он использовал быть вызванным для экземпляра Object извне, до моего преобразования, и это является источником моего замешательства.
Потому что попытка запустить приложение, подобное этому, дает мне исключение на decodedUsers.forEach { userData[it] }
, потому что
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.bla.bla.bla.MainActivity}: java.lang.RuntimeException: Cannot create an instance of class com.bla.bla.bla..user.service.UserManager
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
...
Caused by: java.lang.RuntimeException: Cannot create an instance of class com.,bla.blab.bla.user.service.UserManager
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:275)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106)
...
at com.,bla.blab.bla.app.ui.MainActivity.getUserManager(Unknown Source:7)
at com.,bla.blab.bla.app.ui.MainActivity.onCreate(MainActivity.kt:71)
at android.app.Activity.performCreate(Activity.java:7802)
...
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:267)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106)
...
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.Map.get(java.lang.Object)' on a null object reference
at com.bla.bla.bla.user.service.UserManager.restoreActiveUsers(UserManager.kt:178)
at com.bla.bla.bla.user.service.UserManager.<init>(UserManager.kt:60)
Я проверил с помощью отладчика, и userData
действительно есть null
.
Но это не имеет смысла.
Потому что у меня не было других идей, несмотря на Протесты AndroidStudio, я переключился на вспомогательный конструктор.
constructor(application: Application) : super(application) {
restoreActiveUsers()
}
И это добилось цели.
Я пытаюсь понять, почему, хотя.
Согласно jvm specs :
Всякий раз, когда создается новый экземпляр класса, для него выделяется пространство памяти с местом для всех переменных экземпляра, объявленных в типе класса, и всех переменных экземпляра, объявленных в каждый суперкласс типа класса, включая все переменные экземпляра, которые могут быть скрыты (§8.3).
Если недостаточно места для выделения памяти для объекта, то создание экземпляра класса завершается внезапно с OutOfMemoryError. В противном случае все переменные экземпляра в новом объекте, в том числе объявленные в суперклассах, инициализируются значениями по умолчанию (§4.12.5).
Непосредственно перед возвращением ссылки на вновь созданный объект в качестве результата указанный конструктор обрабатывается для инициализации нового объекта с использованием следующей процедуры:
Назначьте аргументы конструктора для вновь созданных переменных параметров для этого вызова конструктора.
Если этот конструктор начинается с явного вызова конструктора (§8.8.7.1) другого конструктора в том же классе (с использованием этого), то оцените аргументы и обработайте этот вызов конструктора рекурсивно, используя эти же пять шагов. Если этот вызов конструктора завершается внезапно, то эта процедура завершается преждевременно по той же причине; в противном случае перейдите к шагу 5.
Этот конструктор не начинается с явного вызова конструктора другого конструктора в том же классе (используя это). Если этот конструктор предназначен для класса, отличного от Object, тогда этот конструктор начнется с явного или неявного вызова конструктора суперкласса (с использованием super). Оцените аргументы и обработайте этот вызов конструктора суперкласса рекурсивно, используя те же пять шагов. Если этот вызов конструктора завершается преждевременно, то эта процедура завершается преждевременно по той же причине. В противном случае перейдите к шагу 4.
Выполните инициализаторы экземпляров и инициализаторы переменных экземпляра для этого класса, назначив значения инициализаторов переменных экземпляра соответствующим переменным экземпляра слева направо. правильный порядок, в котором они появляются в исходном коде для текста. Если выполнение любого из этих инициализаторов приводит к исключению, то дальнейшие инициализаторы не обрабатываются, и эта процедура резко завершается с тем же исключением. В противном случае перейдите к шагу 5.
Выполните оставшуюся часть этого конструктора. Если это выполнение завершается внезапно, то эта процедура завершается преждевременно по той же причине. В противном случае эта процедура завершается нормально.
Если я правильно читаю, переменные экземпляра всегда должны инициализироваться перед выполнением тела конструктора.
Это будет означать, что init{...}
выполняется перед конструктором.
Но это также не имеет смысла, поскольку согласно эти документы ,
Компилятор Java копирует блоки инициализатора в каждый конструктор.
, который будет выполнять их после инициализации переменной экземпляра, нет?
Итак ... что здесь происходит?
Почему userData
в классе выше null
, когда это не должно быть?