前言
最近在看一些 Java 源码或 Kotlin 库反编译的 Java class 文件之后,看到了一种之前不曾用过的 Java 语法,标签。本文就使用标签的原因及用法,做一些简单的的总结。
什么是 Java 中的标签
说来惭愧,在去搜 Java 标签这个字眼之前,我甚至都不知道 Java 当中有标签这种语法。那么,什么是标签呢?
kotlin 协程中的标签
作为 Android 开发者,如果你用过 Kotlin 的协程,并尝试去了解过他的内部原理,那么你一定见过标签。
kotlin
suspend fun getPeople(): String {
withContext(Dispatchers.IO) {
delay(1000)
}
return "mike"
}
suspend fun getAge(name: String): Int {
withContext(Dispatchers.IO) {
delay(1000)
}
return 100 + name.hashCode()
}
比如上面这两个支持挂起的 suspend 方法。我们用 AS 去查看他反编译的 Java 代码
上面的 label20:
就是标签,当然这两个标签没有任何关系。
Android ViewBinding 中的标签
随着 kotlin-android-extensions 这个 plugin 被官方废弃,为了不再写 findViewById 的模板代码,又开始使用 ViewBinding 了。那么 ViewBinding 背后的实现又是什么呢?
我们可以在 application-module 的 app/build/generated/data_binding_base_class_source_out/debug/out/{package_name}/
下看到所有自动生成的 XXXViewBinding 文件;这里随便找一个文件,看他的 binding 方法。
java
public static ActivityFlutterRootBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.flutter_activity;
Button flutterActivity = rootView.findViewById(id);
if (flutterActivity == null) {
break missingId;
}
id = R.id.flutter_container;
FrameLayout flutterContainer = rootView.findViewById(id);
if (flutterContainer == null) {
break missingId;
}
return new ActivityFlutterRootBinding((LinearLayout) rootView, flutterActivity,
flutterContainer, flutterFragment);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
可以看到这里的 missingId:
就是一个标签。(注意这里最后定义的 String missingId
和这个标签没有任何关系,这是恰好同名而已。
看起来都是在循环中使用,那么为什么要使用标签呢?他解决了什么问题呢?
为什么使用标签
说起标签,就不得不提 goto ,学过 C 语言的同学应该至少看到过这个关键字,但应该是都没有用过。记得大一上计算机课,老师讲到这个 goto 的时候,非常严肃的说,你们只要知道有 goto 这么个东西就可以了,不要在代码里使用他。
于是乎在后来学习 Java 的时候,当每次看到 goto 的章节时,也是没仔细看就跳过了。可能因此就和标签错过了。🤣 🤣 🤣 🤣 🤣 🤣 。计算机课上老师给出的理由是使用 goto 语句会让代码的跳转逻辑变得混乱。
在 Java 中,在 break 和 continue 这两个关键字的身上,我们仍能看出一些 goto 的影子。它们并不属于一次跳转,而是中断循环语句的一种方法。
对 Java 来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方 ------ 在标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环或者一个开关。这是由于 break 和 continue 关键字通常只中断当前循环,但若搭配标签一起使用,它们就会中断并跳转到标签所在的地方开始执行。代码示例:
java
label1:
outer-iteration {
inner-iteration {
// ...
break; // [1]
// ...
continue; // [2]
// ...
continue label1; // [3]
// ...
break label1; // [4]
}
}
- [1] break 中断内部循环,并在外部循环结束。
- [2] continue 移回内部循环的起始处。但在条件 3 中,continue label1 却同时中断内部循环以及外部循环,并移至 label1 处。
- [3] 随后,它实际是继续循环,但却从外部循环开始。
- [4] break label1 也会中断所有循环,并回到 label1 处,但并不重新进入循环。也就是说,它实际是完全中止了两个循环。
以上内容引用自 《On Java8》 。这个简短的例子把标签 的作用描述的淋漓尽致。
这里需要纠正一下,其实除了在循环以外,代码块也可以使用标签,上面 Kotlin Coroutine 和 ViewBinding 的例子都是在代码块之前使用标签。
简单理解,从退出循环的角度出发, continue label 和 break label 都可以用 return 语句实现。但是 return 会退出整个方法体,而 continue 或 break 标签并不会,只是回退到标签的位置重新开始或整体跳过标签继续执行后面的代码。 ,也就是说当我们只是想跳过循环的某些变量或者是跳出循环继续执行后续逻辑时,就可以用标签语法了。其实合理使用标签语法会让整个逻辑更好理解。
标签实现了跳过方法体中某段固定逻辑的功能。
比如 ViewBinding 的 bind 实现
java
public static ActivityFlutterRootBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.flutter_activity;
Button flutterActivity = rootView.findViewById(id);
if (flutterActivity == null) {
break missingId;
}
id = R.id.flutter_container;
FrameLayout flutterContainer = rootView.findViewById(id);
if (flutterContainer == null) {
break missingId;
}
return new ActivityFlutterRootBinding((LinearLayout) rootView, flutterActivity,
flutterContainer, flutterFragment);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
这里并没有采用遍历当前布局中所有 view 的方式,而是按照定义的内容去执行 findViewById 。因此,这里使用 label 是一个非常便捷的实现,这里标签语法有点类似于布尔条件中的与,有任意空值时,直接跳过标签所在的代码块,当然,也可以把这个方法改造成带返回值的方法,在调用这个方法的位置根据返回值决定是否抛出异常。
协程中的实现也是类似的原因,由于在一个协程体内内部可以调用另一个协程方法,因此会有多个标签需要同时去处理,而如果简单的使用 return 进行退出,将导致代码结构非常繁杂,简单的使用标签,就可以实现跳过整段代码块儿的逻辑。了解了标签的语法规则,再去看协程的实现原理就会清晰很多。
总结
Kotlin 协程和 ViewBinding 中对标签的使用逻辑,是非常好理解的,即便是对不了解标签的人,也是基本上可以一句话解释清楚的逻辑。但是,如果为了使用标签而强行在各个地方用标签,可能会无谓的增添程序的复杂性,阅读起来非常难以理解。在不同类型语言中,类似的语法有时会有不同的效果,不能一概而论。对于 C 语言这类面向过程的语言, goto 语句使用不当的确会让整个代码的跳转逻辑变得混乱。但是在 Java 这类面向对象的编程语言中,类似的语法却很值得使用。