记代码不规范+final使用不当引发的血案

​ 在日常开发中,使用final去修饰类、方法、成员变量等都是很常见的,final修饰一个类,表示这个类不能被继,也就是被它修饰的类没有子类;final修饰方法,表示这个方法不能被重写;final修饰变量,表示这个变量不能被修改。但是在使用过程中,可能就会因为使用不规范,导致一系列的意外发生;而我恰好遇到了。

​ 事情是这样子的,我正在维护一个经历N个同学的项目,维护过程中发现某日志分析平台中,我们的应用偶发很多【无理由】的java.lang.ArrayIndexOutOfBoundsException,而且在不同的列表页面都偶然蹦出一两个,而且全是在RecycleView.Adapter中出现的。

​ 撸了一遍代码逻辑,很奇怪,除了代码稍微不是很规范之外,重头到尾并未对数据源做任何的删减动作,一切规规矩矩,难道是推送或者是连续两次点击导致打开同一个页面,onNewIntent()没有处理好,用户手速太快导致数据源不一致?

​ 带着这个疑问,我照着项目中的代码撸了个demo,起先并未注意代码中有个final修饰,经过各种模拟,死活没有得到我想要的答案,贼心不死的我再仔细对比了代码的差异性,一个final引起了我的注意,该不会它才是罪魁祸首吧,越想越觉得有可能,模拟数据变化的情况,果然就是它。

  • 问题代码是这样子的
 public void setData(View view) {
        is = !is;
   			// 数据源用final修饰
        final List<String> strings = new ArrayList<>();
        for (int i = 0; i < (is ? 5 : 7); i++) {
            strings.add(is ? "原数据" : "替换" + i);
        }
        if (myAdapter == null) {
          	// 第一次初始化的时候 数据源指向strings@1(暂时这么叫着)
            myAdapter = new MyAdapter(strings);
            myAdapter.setOnClickListener(new MyAdapter.OnClickListener() {
                @Override
                public void onClick(int position) {
                  	// 此时在监听回调的作用域中指向了strings@1
                    Toast.makeText(MainActivity.this, strings.get(position), Toast.LENGTH_SHORT).show();
                }
            });
            mRcl.setAdapter(myAdapter);
        } else {
          	// 第二次调用该方法时 重新为adapter设置了数据源strings@2
            // 第二次调用该方法时 重新为adapter设置了数据源strings@2
          	// ...
          	// 重点来了 我们的Adapter从始至终只初始化了一次 而我们点击监听回掉中依然指向的是string@1
          	// 在拿着string@1去做string@n的事,数组越界、数据不准自然而然就接踵而来
            myAdapter.setData(strings);
        }


    }
复制代码

总结

  • 代码风格要规范,像这种如案例中myAdapter、strings如果在页面进入就需使用的可以声明为成员变量private final List<String> data;private final MyAdapter mMyAdapter;,可以在声明变量时就初始化,也可以在onCreate之后初始化。像在代码中去对myAdapter的判null处理就完全没有必要了,在数据源发生更改的时候使用 data.clear()data.addAll()的方法对数据源进行更改。
  • 在类似的回调中,时刻注意我们所持有的引用和我们实际数据源的引用是否是同一个,否侧容易造成这种不可忽视却又不容易查找到的bug。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享