记一次踩坑:Java类加载顺序

摘要

记一次因为Java基础不牢固,导致踩坑的经历。

背景

  因为考拉项目UI升级,但因为新版本UI变动太大,怕考虑不周出问题,所以保留以前版本,也就是要在UI上做ABtest,可以通过开关一键切换。

  商品详情页涉及改动面略大,并且旧版产品承诺说会尽快下线,同时时间特别紧张,所以在商详页的改版中,遵循的原则是最小改动,尽量不将新版UI和老版耦合,如果某些模块已经是独立的自定义View,那么重新创建新的,而且因为历史遗留问题,外部Fragment或者Activity存在全局变量的引用,而且有多处,所以我们把新的自定义View继承旧版自定View,复制所有的局部变量和方法,以便后续下线旧版直接删除类文件即可。

部分代码如下

以下为旧版底部购买栏BottomBuyView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class BottomBuyView extends LinearLayout {
private ForbidFastClickListener mClickListener = new ForbidFastClickListener() {
@Override
public void onForbidFastClick(View view) {
onViewClick(view);
}
};
private boolean isPunctuality;
private boolean isFactory;
private boolean isDeposit;
private boolean isTimeSale;
public BottomBuyView(Context context) {
this(context, null);
}
public BottomBuyView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BottomBuyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViews();
}
protected void initViews() {
inflate(getContext(), R.layout.bottom_buy_view, this);
}
}

为了最小改动,我们新写一个底部购买栏BottomBuyViewNew

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class BottomBuyViewNew extends BottomBuyView {
private ForbidFastClickListener mClickListener = new ForbidFastClickListener() {
@Override
public void onForbidFastClick(View view) {
onViewClick(view);
}
};
private boolean isPunctuality;
private boolean isFactory;
private boolean isDeposit;
private boolean isTimeSale;
public BottomBuyViewNew(Context context) {
this(context, null);
}
public BottomBuyViewNew(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BottomBuyViewNew(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void initViews() {
inflate(getContext(), R.layout.bottom_buy_view_new, this);
}
}

以上代码可以注意下第三个构造方法,在BottomBuyView中调用initView();在BottomBuyViewNew中只是调用了super(),然后复写initView()来实现替换View的方式,ForbidFastClickListener为防止快速点击的View.OnClickListener的实现类。

重点来了

发现BottomBuyViewNew的点击事件一直无法生效,debug发现mClickListener为null….

百思不得其解,一开始以为是子类和父类有一个同名的全局变量导致的,于是换了个变量名发现依然无法响应点击事件。

那么子类父类局部变量、构造函数的加载顺序到底是怎样?

参考文章Java中父类与子类的加载顺序详解

  1. 父类非静态语句块1

  2. 父类非静态语句块2

  3. 父类构造函数

  4. 子类非静态变量

  5. 子类非静态语句块1

  6. 子类非静态语句块2

  7. 子类构造函数

很明显这个顺序就能说明mClickListener为什么为null,因为initView()是复写的父类的方法,所以在父类的构造函数中就调用了,此时还没有加载子类的非静态变量,所以mClickListener为null。

为了规避这个问题,我们修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class BottomBuyViewNew extends BottomBuyView {
private ForbidFastClickListener mClickListener = new ForbidFastClickListener() {
@Override
public void onForbidFastClick(View view) {
onViewClick(view);
}
};
private boolean isPunctuality;
private boolean isFactory;
private boolean isDeposit;
private boolean isTimeSale;
public BottomBuyViewNew(Context context) {
this(context, null);
}
public BottomBuyViewNew(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BottomBuyViewNew(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViewNew();
}
@Override
protected void initViews() {
}
private void initViewNew() {
inflate(getContext(), R.layout.bottom_buy_view_new, this);
}
}

结论

基础知识能力需加强,一不小心就容易踩坑。

参考

  1. Java中父类与子类的加载顺序详解