- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
所以我正在重写 Android 应用程序的一些遗留代码。
该更改的一部分包括引入 View 模型。其中的一部分包括将曾经是对象
的UserManager
类更改为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
block 是新的,因为在我进行转换之前,它曾经在对象实例上被调用,这是我困惑的根源。
因为尝试像这样运行应用程序会给我带来异常 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 :
Whenever a new class instance is created, memory space is allocated for it with room for all the instance variables declared in the class type and all the instance variables declared in each superclass of the class type, including all the instance variables that may be hidden (§8.3).
If there is not sufficient space available to allocate memory for the object, then creation of the class instance completes abruptly with an OutOfMemoryError. Otherwise, all the instance variables in the new object, including those declared in superclasses, are initialized to their default values (§4.12.5).
Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:
Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.
If this constructor begins with an explicit constructor invocation (§8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; otherwise, continue with step 5.
This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.
Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5.
Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.
如果我没看错的话,实例变量应该总是在构造函数体执行之前初始化。
这意味着 init{...}
在构造函数之前执行。
但这也没有意义,因为根据 these docs ,
The Java compiler copies initializer blocks into every constructor.
这会让它们在实例变量初始化之后执行,不是吗?
那么......这里发生了什么?
为什么userData
出现在上面的类null
中,而它不应该是这样?
最佳答案
在 Kotlin 端找不到任何错误,可能是 Android 行为。
During an instance initialization, the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers
Check out the code example in kotlindoc
Note that code in initializer blocks effectively becomes part of the primary constructor. Delegation to the primary constructor happens as the first statement of a secondary constructor, so the code in all initializer blocks and property initializers is executed before the secondary constructor body. Even if the class has no primary constructor, the delegation still happens implicitly, and the initializer blocks are still executed
您的代码应该执行
第一UserManager(application: Application)
,
然后AndroidViewModel(application)
,
然后private val userData: MutableMap<User, MutableMap<String, Any>> = ...
,
然后init { restoreActiveUsers() }
我尝试在 EDI 中编写此示例(扩展普通类而不是 AndroidViewModel
),但我无法重现该异常:
private open class Boo (private val input: Int)
private class Foo : Boo(1) {
private val logger = LoggerFactory.getLogger(this::class.java)
val userData: MutableMap<String, MutableMap<String, Any>> = object : HashMap<String, MutableMap<String, Any>>() {
override fun get(key: String): MutableMap<String, Any>? {
val former = super.get(key)
val current = former ?: mutableMapOf()
if (current !== former) this.put(key, current)
return current
}
}
init {
restoreActiveUsers()
}
private fun restoreActiveUsers() {
(1..3).forEach { _ -> logger.info { "${userData["notInside"]}" } }
}
}
输出:
{}
{}
{}
-
您的问题表明行为非常奇怪,因为字段 userData
是一个不可为空 val
并且不会产生编译错误,如果将 init block 写在该字段上方,则会产生编译错误!因此,当纯粹涉及 kotlin 时,必须先初始化该字段。
我没有 Android 开发经验,也不知道初始化部分是如何工作的,但我强烈建议在那里查找问题。
关于java - 为什么主构造函数主体在字段初始化之前执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59914168/
我是 Spring 新手,这就是我想要做的事情: 我正在使用一个基于 Maven 的库,它有自己的 Spring 上下文和 Autowiring 字段。 它的bean配置文件是src/test/res
我在我的测试脚本中有以下列表初始化: newSequenceCore=["ls", "ns", "*", "cm", "*", "ov", "ov", "ov", "ov", "kd"] (代表要在控
这个问题在这里已经有了答案: 关闭 11 年前。 Possible Duplicate: Class construction with initial values 当我查看 http://en.
我得到了成员变量“objectCount”的限定错误。编译器还返回“ISO C++ 禁止非常量静态成员的类内初始化”。这是主类: #include #include "Tree.h" using n
我有如下所示的a.h class A { public: void doSomething()=0; }; 然后我有如下所示的b.h #include "a.h" class b: publi
我需要解析 Firebase DataSnapshot (一个 JSON 对象)转换成一个数据类,其属性包括 enum 和 list。所以我更喜欢通过传递 DataSnapshot 来手动解析它进入二
我使用 JQuery 一段时间了,我总是使用以下代码来初始化我的 javascript: $(document).ready( function() { // Initalisation logic
这里是 Objective-C 菜鸟。 为什么会这样: NSString *myString = [NSString alloc]; [myString initWithFormat:@"%f", s
我无法让核心数据支持的 NSArrayController 在我的代码中正常工作。下面是我的代码: pageArrayController = [[NSArrayController alloc] i
我对这一切都很陌生,并且无法将其安装到我的后端代码中。它去哪里?在我的页脚下面有我所有的 JS? 比如,这是什么意思: Popup initialization code should be exec
这可能是一个简单的问题,但是嘿,我是初学者。 所以我创建了一个程序来计算一些东西,它目前正在控制台中运行。我决定向其中添加一个用户界面,因此我使用 NetBeans IDE 中的内置功能创建了一个 J
我有 2 个 Controller ,TEST1Controller 和 TEST2Controller 在TEST2Controller中,我有一个initialize()函数设置属性值。 如果我尝
据我所知, dependentObservable 在声明时会进行计算。但如果某些值尚不存在怎么办? 例如: var viewModel ={}; var dependentObservable1 =
我正在阅读 POODR 这本书,它使用旧语法进行默认值初始化。我想用新语法实现相同的功能。 class Gear attr_reader :chainring, :cog, :wheel de
我按照 polymer 教程的说明进行操作: https://www.polymer-project.org/3.0/start/install-3-0 (我跳过了可选部分) 但是,在我执行命令“po
很抱歉问到一个非常新手的Kotlin问题,但是我正在努力理解与构造函数和初始化有关的一些东西。 我有这个类和构造函数: class TestCaseBuilder constructor(
假设我们有一个包含 30 列和 30 行的网格。 生命游戏规则简而言之: 一个小区有八个相邻小区 当一个细胞拥有三个存活的相邻细胞时,该细胞就会存活 如果一个细胞恰好有两个或三个活的相邻细胞,那么它就
我是 MQTT 和 Android 开放附件“AOA” 的新手。在阅读教程时,我意识到,在尝试写入 ByteArrayOutputStream 类型的变量之前,应该写入 0 或 0x00首先到该变量。
我有 2 个 Controller ,TEST1Controller 和 TEST2Controller 在TEST2Controller中,我有一个initialize()函数设置属性值。 如果我尝
我有一个inotify /内核问题。我正在使用“inotify” Python项目进行观察,但是,我的问题仍然是固有的关于inotify内核实现的核心。 Python inotify项目处理递归ino
我是一名优秀的程序员,十分优秀!