【问题标题】:Why Activity.findViewById result type is inferred as a platform type?为什么将 Activity.findViewById 结果类型推断为平台类型?
【发布时间】:2019-03-12 11:04:00
【问题描述】:

我是 Android 开发和 Kotlin 的新手。

我正在学习第一个使用 Kotlin 的 Android 教程,并且我有这个方法调用:

val editText = findViewById<EditText>(R.id.editText)

其中findViewByIdandroid.app.Activity 中定义为:

@Nullable
public <T extends View> T findViewById(@IdRes int id)

Android Studio 告诉我editText 被推断为EditText! 类型。我阅读了有关 Java 互操作性和平台类型的 Kotlin 文档,但由于 findViewById 使用 android.annotation.Nullable 进行了批注,这是支持的可空性批注列表中的 (according to Kotlin documentation),我预计 editText 类型会被推断为EditText?.

如果我将代码更改为:

val editText = findViewById<EditText?>(R.id.editText)

但为什么需要这样做而 @Nullable 却被忽略了?

【问题讨论】:

  • 该方法可以为空,而不是类规范。我很确定 Kotlin 在内部将 findViewById 转换为括号中的内容。
  • 您自己在findViewById&lt;EditText&gt;(R.id.editText) 中输入了EditText 吗?还是 IDE 自动完成的?
  • @CommonsWare 有什么不同吗?
  • 如果你自己输入,那么你就是说findViewById()返回EditText而不是EditText?。 Kotlin 不会推翻您,因为它假定您知道自己在做什么。相反,如果您是从某个工具获得的,那么该工具可能存在错误,它应该注意@Nullable 注释。

标签: android kotlin type-inference


【解决方案1】:

该方法可以为空,但您的类规范不是。我已经反编译了我的一个 APK 并发现了以下内容。

这是一个简单的 Kotlin 方法:

private fun setAppName() {
    val name = view.findViewById<TextView>(R.id.app_name)
    name.text = appInfo.appName
}

你可以看到name的规范是一个TextView。

该方法编译后变成如下:

private final void setAppName() {
    TextView textView = (TextView) this.view.findViewById(R.id.app_name);
    Intrinsics.checkExpressionValueIsNotNull(textView, "name");
    AppInfo appInfo = this.appInfo;
    if (appInfo == null) {
        Intrinsics.throwUninitializedPropertyAccessException("appInfo");
    }
    textView.setText(appInfo.getAppName());
}

请注意,它 findViewById 的返回值完全转换为括号中的内容。在你的情况下,EditText。

基本上,这种演员阵容会把一切都搞砸了。这将类似于这样做:

val view: View? = findViewById(R.id.whatever)
val castView: TextView = view as TextView

由于 Kotlin 处理类型推断的方式,它有效地绕过了 @Nullable

【讨论】:

  • Java 中没有“[non-]nullable”类的概念。很清楚 Kotlin 在做什么,但我不明白为什么。如果您在 Java 中有 @Nullable String foo() { ... } 方法,并且从 Kotlin 调用该方法,则推断的类型是 String?,而不是 String!,这就是可空性注释的目的。
  • 因为可空性在方法上。看看 Kotlin 编译后的样子。你有findViewById(),它返回一个可以为空的接收器,但是你将它转换为一个非空的TextView。 IDE lint 并不是那么聪明。最多只能通过1到2个级别指出潜在问题。
  • 对不起,我不关注。这里我们有@Nullable public &lt;T extends View&gt; T findViewById(@IdRes int id),所以在方法上也声明了可空性。 &lt;EditText&gt; 应该是类型捕获提示,而不是强制类型转换...
  • 看我贴的Java代码。 Kotlin 编译为 Java 字节码,并在编译期间添加了自己的 null 检查辅助方法。你可以很清楚地看到findViewById&lt;EditText&gt;() 就是(EditText) findViewById()。由于你不使用EditText?,所以Kotlin 不会在其中添加null-check 方法。
  • 那么,我们同意这是 Kotlin 编译器的错误吗? findViewById 的结果类型是 T?,无论 T 是什么。对于&lt;EditText&gt;,我说TEditText,所以我希望它返回EditText?。相反,如果我们说&lt;EditText&gt; 表示一个演员表,那么结果应该是EditText 类型而不是EditText!。现在,Kotlin 只是简单地将 T 替换为 EditText 并忽略 @Nullable 注释。
【解决方案2】:

android.app.Activity 上设置的实际注解是@android.annotation.Nullable。不可为空的对应项是@android.annotation.NonNull。这些不在 Kotlin (see documentation) 当前支持的“Android 可空性注释”之列,而是在包 com.android.annotationsandroid.support.annotations 中声明。

我打开了问题 KT-27566 并发现这些注释当前被忽略,因为它们具有源保留,因此它们在 Kotlin 编译器处理的编译字节码中不可用。

此外,根据KT-25279,似乎还有其他此类注释,而是定义在包com.android.support中,它们遇到了同样的问题。

我真的不明白为什么 Google 会在可空性注释上搞得这么乱(这么多不同的变体,其中一些对 Kotlin 集成毫无用处,而它们的用处却是最大的......)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-05
    • 1970-01-01
    • 2023-03-15
    • 2022-08-03
    相关资源
    最近更新 更多