- 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/
我试图将护照逻辑放入 Controller 文件中,但问题是当我将逻辑放入 Controller 中时,它告诉我“无法读取未定义的属性“主体””,但是当我将代码移至索引时,所有内容都会路由向右走 in
我正在学习 Javascript,我正在尝试创建一个简单的下拉菜单。我想要的功能的一个例子可以在谷歌主页的顶部菜单中看到,带有“更多”和“设置”下拉菜单。具体来说,当您单击关闭菜单时,菜单会消失。 我
我正在努力让 Swagger 正确呈现我的 ServiceStack 服务。 我希望看到一个 UserId 字符串作为表单参数,一个 PrivateCustomer 对象作为主体参数,但是尽管 Use
注意:由于随后的研究,这个问题已经完全重组。 我正在尝试从 Shiro 的主题 PrincipalCollection 中检索值.我在集合中添加了两个主体。 Username和 UUID .当我试图记
我们正在开发一个将 OAuth 2 用于两个用例的应用程序: 访问后端微服务(使用 client_credentials) 验证应用程序的用户(使用 authorization_code ,因此将用户
我有这段代码生成一个将 myNumber 乘以 5 的委托(delegate) ParameterExpression numParam = Expression.Parameter(typeof(i
我有一些jquery, $( document ).ready(function() { body=$(body).html; $("html").html(body); }); 这应
我创建了一个通用异常 DTO,它也扩展了 RuntimeException。通过这种方式,可以在应用程序中使用它,也可以将其用作 DTO。问题是当我将 DTO 应用于 ResponseEntity 构
在 Angular 5 HttpClient 中,我可以通过这种方式设置 HttpParams()。 const body = new HttpParams() .set('email', '
我正在从 RabbitMQ 读取数据,如下所示: connection = factory.newConnection(); ch = connection.createChannel() ; Str
如何使用不同类型的调用和响应主体来改造 PUT?我有一个错误限制。类型必须相同 and 。响应bodie可以包含int值,但call不应该,因为当我用int值初始化CallBody对象时,它已经包
原则上我想做这样的事情: #grab some value from outer source (i.e. file or list defined by another programer) set
我知道如何使用TextureRegions 创建动画并将其应用于非box2d 游戏中的对象。 但是在 libgdx 的 box2d 中,我不知道该怎么做。在CocosD2中,Sprite对象中有run
我有这段代码生成一个将 myNumber 乘以 5 的委托(delegate) ParameterExpression numParam = Expression.Parameter(typeof(i
我已经计算了花括号的数量,但无法弄清楚为什么类主体不完整。每次我试图修复类(class)时,都会把整个类(class)弄乱。问题出在代码中的最后一个类。最后一个花括号给我带来了类里面的麻烦。我正在使用
有人知道吗?我只能看到 ApplyTorque 和 SetAngularVelocity,我只想在将对象添加到模拟之前旋转对象,例如:所以我有一个 crate 倾斜靠在墙上,另一个 crate 是平的
我可以获得如何让图像出现在 box2d 主体上的简单答案吗?我尝试为图像和主体创建 x 和 y int,但是一旦主体移动,图像就会保持静态。如果您确实回答,请尽可能解释一下代码。如果您对我的完整源代码
我知道我可以通过使用 PolygonRegion 来做到这一点,但问题是我使用 scene2d.Stage 和几个 Actor 。您可能知道阶段使用 SpriteBatch 而我无法渲染 Polygo
您好,我有以下代码: function redirect(){ window.location.href='logged_out_chat.php'; } ...在我的标题和以下正文标记中:
我在 didBegin(contact:) 中触发了 SpriteKit 物理接触。我为要移出屏幕的 Dot 对象的实例抓取物理体,但是当我尝试像这样更改其位置时,没有任何反应: 第一种方法 /* I
我是一名优秀的程序员,十分优秀!