加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

Groovy / Java Gotcha 一则

发布时间:2020-12-14 17:03:19 所属栏目:大数据 来源:网络整理
导读:今天帮人捉虫,看到一个很有趣的关于数组切片的陷阱。代码如下: 1: list = [1,2,3] 2: subList = list[1..-1] 3: ...很多很多行代码,但“没有”修改过list 4: ? 5: subList.add(0,10) 6: assert subList == list 按那个程序员的想法,subList 的初始值是 [

今天帮人捉虫,看到一个很有趣的关于数组切片的陷阱。代码如下:

   1: list = [1,2,3]
   2: subList = list[1..-1]
   3: ...很多很多行代码,但“没有”修改过list
   4:? 
   5: subList.add(0,10)
   6: assert subList == list

按那个程序员的想法,subList 的初始值是 [2,3],往首位塞个 1,不就变成了 [1,3] 吗?应该和 list 相等才对。

可惜,Groovy 不是这样认为的,断言毫不客气的失败了。

原因在于 subList 的类别,查看 subList.class 会发现这是一个 java.util.RandomAccessSubList (是个 private 类)。RandomAccessSubList 包含了三个值域,分别是

  • backingList: 包含了原有列表的 reference
  • offset: 从第几个偏移量开始切片
  • size: 子列表的长度

从这个结构你大概就能猜出问题之所在了,subList 并没有在内存中重新分配一组空间来保存一个真正的子列表,而是采用了类似指针的技术“指出”了子列表所在的位置。其结果是,当调用 add 函数的时候,Groovy 其实是以 add 的第一参数加上 offset 为插入位置并在 list 内插入了元素 1,同时将 subList 的 size 值加 1。所以,最后的 list 为 [1,10,3],而 subList 为 [10,3]。

这个陷阱的另一个形式是:

3: // ... N 行代码
   4: list.remove(0)
   5: // 又是 N 行代码
   6: println subList

Opps,这一次则是抛出 java.util.ConcurrentModificationException。(原因很简单,不说了)

由于这个陷阱只能在运行时被发现(我猜是不是有些 BUG 查找软件能找到这样的问题),因此,只有严密的单元测试才能发现漏洞。如果在你的单元测试没有能覆盖这里,嘿嘿……那就可能是一年以后在你哪个VIP客户的服务器上爆炸的定时炸弹了。(一般没有单元测试的代码都写的很长很长很长,要定位这样的问题绝对不简单啊)

PS:这也是为什么我很多时候不计代价的使用 clone 来复制对象或是重新逐个元素地生成集合的原因。在 Scala 中,由于 List 是不可变的(Map 与 Set 则具备了可变和不可变的版本),从而也就很好的避免了这样的问题。如果你的 api 库里面有第三方的不可变集合库,好好利用它们。

Technorati Tags: Groovy

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读