前言
自 2019 年以来,Kotlin 是 Android 应用程序的首选语言。两年前,谷歌宣布在 Android Studio 中支持 Kotlin。从那时起已经过去了相当长的一段时间。那么,为什么 2021 年夏天的一篇文章仍然涉及Android 应用程序中的现代 Java ?
因为 Java 仍然存在于许多 Android 应用程序中。特别是在 2017 年之前发布的那些。当然,许多已经部分或完全转换为 Kotlin。但其他人没有。而且,应用程序越旧,使用的 Java 风格就越旧。Apache Harmony(其类库进入 Android)始于 Java 1.5。所以,非常古老的应用程序看起来像这样:大量的匿名内部类。StringJava 7的 s in switchstatements 或 try-with-resources 等功能需要很长时间才可AutoCloseable用。Java 8 的 Lambda 和流也没有立即可用。
因此,Android 应用程序中的 Java 代码可能看起来比它需要的要旧。我们当然可以说,只要该代码不会造成麻烦,我们最好不要管它。但如果它仍然是应用程序的重要组成部分,我们应该像对待新代码一样对待它:关心它。这可能意味着:
- 将其转换为 Kotlin
- 利用现代 Java 特性
我不打算比较这些方法,因为这本身就是一篇文章。😀 相反,我将专注于后一种选择。但在我们深入研究之前:这不是关于 Java 与 Kotlin,当然也不是关于争论哪种编程语言更好。我只是认为任何代码都应该尽可能好。
设置
语言版本在模块级build.gradle文件中配置。
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
useIR = true
}
...
要使用 Java 11,我们还需要配置Android Gradle 插件 7.0,顺便说一下,它需要Java 11。这是在项目级build.gradle文件中完成的。
buildscript {
...
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.0"
...
但为什么是 Java 11?我们在 3 月份没有看到 Java 16 吗?Java 11 是继 Java 8 之后的第一个长期支持 (LTS) 版本。Oracle 于 2019 年 1 月停止支持 Java 8。下一个 LTS 版本将是 Java 17,计划于 2021 年 9 月发布。
有什么存货?
作为 Kotliner,我们喜欢写类似
var s = resources.getString(R.string.app_name)
或者val,如果s不改变。
从 Java 10开始,这也是可能的:
var s = context.getString(R.string.app_name);
如果需要,您可以进行s最终处理。我们在这里看到的称为局部类型推断。不错,不是吗?但是请记住,它不能在块、方法和构造函数之外工作。
Java 11 添加var了 lambda 参数。
var list = Arrays.asList("Hello", "Android");
var result = list.stream()
.map((var x) -> x.toUpperCase(Locale.getDefault()))
.collect(Collectors.joining(", "));
也有一些新的方法String:isBlank(), lines(), strip(), stripLeading(), stripTrailing(), 和repeat(). 遗憾的是,我们不能使用它们(在撰写本文时)。那是因为 Android 类库是 Java 类库和 Android 特定包的组合。对 OpenJDK 类库的更改不会自动成为 Android 的一部分。相反,它们是手工挑选的。实际上,每个 API 级别都会看到一些正在移植的更改。例如,使用 API 级别 31,我们将compareUnsigned()进入java.lang.Short. 它在 Java 9 中首次亮相。
甚至更老的是 try-with-resources:Java 7 中最酷、可能最少使用的特性之一。看看:
FileInputStream fis = null;
try {
fis = context.openFileInput("myfile.txt");
// do some work
} catch (FileNotFoundException e) {
Log.e(TAG, "something went wrong", e);
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
Log.e(TAG, "something went wrong", e);
}
}
是的,这是样板代码。但是这个怎么样?
try (FileInputStream fis = context.openFileInput("myfile.txt")) {
// do some work
} catch (IOException e) {
Log.e(TAG, "something went wrong", e);
}
如果您想知道…不,我没有忘记close(). 这是自动完成的。下一个…
try {
var fis = context.openFileInput("myfile.txt");
// do some work
} catch (IOException e) {
Log.e(TAG, "something went wrong", e);
} catch (IllegalArgumentException e) {
Log.e(TAG, "something went wrong", e);
} catch (NullPointerException e) {
Log.e(TAG, "something went wrong", e);
}
是的,这里肯定有问题。
为什么不这样呢?
try {
var fis = context.openFileInput("myfile.txt");
} catch (IOException |
IllegalArgumentException |
NullPointerException e) {
Log.e(TAG, "something went wrong", e);
}
从 Java 7 开始,一个catch块可以处理不止一种类型的异常。这可以减少代码重复。而且您不再有catching的解释Throwable。🤣
样板代码的另一个来源是匿名内部类
Button b = findViewById(R.id.button);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// do some work
}
});
我们可以将其缩小为:
Button b = findViewById(R.id.button);
b.setOnClickListener(view -> {
// do some work
});
如您所见,Java 仍然面临的一些偏见(在某种程度上)不再有效。开发人员可以对其代码进行现代化改造。你可以做很多事情。
结论
对旧的 Java 代码进行现代化改造无疑是值得的。首先,它变得更容易阅读。其次,您将能够删除大量样板代码。第三,您的应用程序将变得不那么容易出错。不相信我?老实说,你的代码真的放在close()里面了吗try?catch…嗯,你应该。并且通过 try-with-resources 您可以免费获得它。