近期整理代码,出现了一个空指针异常,其源头为 MapController 类的一个方法:
1 | public void addOnMarkerClickListener(AMap.OnMarkerClickListener onMarkerClickListener) { |
然后看到 kotlin 代码:
1 | class MapControllerTest1(val mapController: MapController) { |
说明在执行 init 时 onMarkClickListener 为 null。编译 kotlin 代码到 Java 代码看看:
1 | public final class MapControllerTest1 { |
可以看到 init 方法是在设置 onMarkClickListener 之前执行的,所以会报空指针异常。
将代码改成如下:
1 | class MapControllerTest1(val mapController: MapController) { |
此时便不会报空指针异常了。编译到 Java 代码查看:
1 | public final class MapControllerTest1 { |
先赋值 onMarkClickListener,才执行 init,很显然不会报异常了。
我们可以理解 init 为初始化代码块,若换成 Java 代码,则不论 onMarkerClickListener 在何处声明,都不会抛异常。
1 | public class MapControllerTest2 { |
给我的感觉就是 Kotlin 初始化顺序与 Java 不同了。查看到 Koltin 文档,看到这样一段:
主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中。
在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起:
1 | class InitOrderDemo(name: String) { |
划重点:初始化块按照它们出现在类体中的顺序执行。所以在 init 之后声明的变量在 init 中是调用不了的。
将代码改成如下,会直接就编译不过:
1 | class MapControllerTest1(val mapController: MapController) { |
此时会提示:Variable ‘onMarkClickListener’ must be initialized。只是凑巧我将 addOnMarkerClickListener 放到了另外一个函数体里面,导致编译通过,规避了这个问题。所以在写 Kotlin 的时候要注意代码的顺序!
再摘一段比较重要的:
请注意,初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块:
1 | class Constructors { |
执行结果:
1 | Init block |
试了一下,将代码改成如下:
1 | class MapControllerTest1(val mapController: MapController) { |
确实会抛异常。
文档很重要呀~
参考:类与继承