前段时间,在做一个商品的分类,分类有3级,类似于以下这种形式的:

  ---食物

    ---蔬菜

      ---白菜

    ---材料

      ---鸡肉

.......

  而我需要做的是将取得的一个商品的字符串类型的分类ID集,然后在前台显示其分类。显示的模式就像这样的:"口味:甜、酸;材料:鸡肉、牛肉..."。商品的分类是可能多选的,而且所选分类也不一定就是一个大分类里面的,所以就必须在取得分类集合后进行同类型分类。而根据同类型分类的依据就是该分类的最大父类与其他分类是相同的。在其获得的分类中,有一个getParent()方法可以获取父类对象,当不存在父类类型的时候,会取得空值。

  我首先想到的是把字符串ID集转换成分类集合是第一步。即String[] productCategories => List<ProductCategory>。这步很简单,不多说明。

  第二步,我最初的想法是,建立一个List<List<ProductCategory>>集合,这个集合里面存放分类集合,一个元素作为同一类型存放。先将转换好的List<ProductCategory>集合copy一份,然后用这两个集合两层循环嵌套比较,

  代码类似下面的这样的:
  

     List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
List<String> list2 = list;
List<List<String>> bigCate = new ArrayList<List<String>>();
for (String str : list) {
List<String> cate = new ArrayList<String>();
cate.add(str);
for (String str2 : list2) {
if(str.equals(str2)){
cate.add(str2);
list.remove(str2);
}
}
bigCate.add(cate);
}
System.out.println(bigCate);

这样就把相同分类比较出来了,然后存进List<List<ProductCategory>>集合中。可是并不是那么简单,其中有两个问题需要解决,也是我对java面向对象理解不深刻的地方。

  其一:java对象都是引用对象,java没有指针,并不是真的没有指针,而是被封装起来了,我们没有接触到而已。所以把集合copy一份,仅仅是把已经存在于stack中的指向存放集合数据的地址copy了一份。所以在执行remove方法时,实际上也移除了list2集合的元素。在行14的操作中我想在copy的集合中移除掉已经分类好的元素,结果报出java.util.ConcurrentModificationException异常。

  其二:java中List集合的安全机制。list集合在增加元素的时候,查看ArrayList的源码中,可以看到以下代码:

   public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

而其中的ensureCapacity(size+1)方法是增加modcount变量值的。即以下代码:

  public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}

  那么我在执行for-each循环的时候,又移除元素(实际上循环遍历与移除都是同一个对象),那么在remove(object)的时候,又是执行什么操作的呢,在源码中发现:

     public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

  其实内部是先用普通for循环遍历,然后if判断找出元素索引,然后再执行fastRemove(index)。那我们再看看这个fastRemove(int index)方法是什么样的:

    private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}

  我们发现它也执行了modCount++操作,那么在增加元素和移除元素的时候,这个modCount都在执行自增操作,那么到这里,你可能已经猜到一些原因了。我们继续查看源码,ArrayList执行for-each迭代时候,实际上是实现了一个Iterator接口的方法,iterator有几个方法,代码如下:

 public interface Iterator<E> {

     boolean hasNext();

     E next();

     void remove();
}

   那么在AbstractList中,它使用了一个内部类来实现Iterator:

 private class Itr implements Iterator<E> {
int cursor = 0; int lastRet = -1; int expectedModCount = modCount; public boolean hasNext() {
return cursor != size();
} public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
} public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification(); try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}

  它在内部定义了一个游标变量cursor,通过游标来获取集合元素并且判断集合是否还有下一个值,确定集合执行hasNext()方法之后还有数据,然后会执行next()方法取得下一个值。我们还观察到,在这个迭代器中第6行,它定义了一个变量int expectedModCount = modCount;那么从我刚才写的那个分类来看,在第二次for-each循环中,在list执行了remove方法之后,modCount已经等于5了。但是在一开始执行for-each迭代器已经将modCount变量赋值给expectedModCount了,那个时候集合还没有执行remove方法,此时modCount=4。那么在next()方法中它首先会调用一个checkForComodification()方法, checkForComodification()方法的代码如下:

  

 final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

  看到没有,它会检查modCount是否等于expectedModCount,如果不相等,说明集合在迭代的时候结构发生了变化,然后抛出ConcurrentModificationException异常。

  整个过程就是这样的。也许看起来是比较简单的,但是我觉得了解java的一些原理和机制,学习一下它里面的数据结构也并不是什么坏事。

  本篇纯原创,转载请注明出处。最近经常用到集合,java为集合提供了很多简洁快捷的方法。所以没事就随意看了下java源码,如有不同见解,欢迎交流!谢谢!

  

一个分类,两个问题之ArrayList的更多相关文章

  1. js字符串长度计算(一个汉字==两个字符)和字符串截取

    js字符串长度计算(一个汉字==两个字符)和字符串截取 String.prototype.realLength = function() { return this.replace(/[^\x00-\ ...

  2. 性能优化(一个)Hibernate 使用缓存(一个、两、查询)提高系统性能

    在hibernate有三种类型的高速缓存,我们使用最频繁.分别缓存.缓存和查询缓存.下面我们使用这三个缓存中的项目和分析的优点和缺点. 缓存它的作用在于提高性能系统性能,介于应用系统与数据库之间而存在 ...

  3. 同一个tomcat多个项目共享session,一个tomcat两个项目共享sessionId

    同一个tomcat多个项目共享session,一个tomcat两个项目共享sessionId >>>>>>>>>>>>>& ...

  4. Python selenium 一个节点两个关联input

    HTML代码: 一个节点两个关联input  多出现于密码框 先需要模拟点击进入第一个input,才能激活第二个input. 代码: driver.find_element_by_name('Text ...

  5. 一个月薪两万的Web安全工程师要掌握哪些技能?

    作为一个薪水两万起步的工作,我想知道这些牛人们都会哪些技能呢? Web安全相关概念.熟悉渗透相关工具.渗透实战操作.关注安全圈动态.熟悉Windows/Kali Linux.服务器安全配置.脚本编程学 ...

  6. 一个label两种颜色,一个label两种字体

    -(void)addLabel{ UILabel *label = [[UILabel alloc]init]; label.backgroundColor = [UIColor grayColor] ...

  7. NSIS编译报错:您可能有有一个或两个(大)的旧临时文件

    一.有时在编译NSIS时会出现如下错误: 注意: 您可能有有一个或两个(大)的旧临时文件 残留在临时目录文件夹中 (通常这种情况只会发生在 Windows 9x 系统中). 二.本人遇到的问题原因: ...

  8. wordpress通过$wpdb获取一个分类下所有的文章

    在wordpress程序根目录下新建一个php文件,粘贴下面的代码 如下面的代码注释,修改$CID这个分类id,就可以获取这个分类下的文章了.这个查询需要联合三个表wp_posts.wp_term_r ...

  9. ApachShiro 一个系统 两套验证方法-(后台管理员登录、前台App用户登录)同一接口实现、源码分析

    需求: 在公司新的系统里面博主我使用的是ApachShiro 作为安全框架.作为后端的鉴权以及登录.分配权限等操作 管理员的信息都是存储在管理员表 前台App 用户也需要校验用户名和密码进行登录.但是 ...

随机推荐

  1. HDU 3449 Consumer

    这是一道依赖背包问题.背包问题通常的解法都是由0/1背包拓展过来的,这道也不例外.我最初想到的做法是,由于有依赖关系,先对附件做个DP,得到1-w的附件背包结果f[i]表示i花费得到的最大收益,然后把 ...

  2. codeforces上某题

    一道codeforces上的题目. 题目大意: 定义有k个不同的字符的字符串为好字符串.现在给出一个字符串,求解对该字符串的每个前缀Si至少是多少个好字符串的连接,若不能由好字符串连接而成则输出-1. ...

  3. memcached 高级机制(一)

    memcached的高级机制 memcached内存机制 (1)我们知道操作系统对进程的处理方法,在多进程并发的操作系统中,程序的执行不可避免的会产生碎片.同样对于memcached,在存储value ...

  4. JavaWeb 文件上传下载

    1. 文件上传下载概述 1.1. 什么是文件上传下载 所谓文件上传下载就是将本地文件上传到服务器端,从服务器端下载文件到本地的过程.例如目前网站需要上传头像.上传下载图片或网盘等功能都是利用文件上传下 ...

  5. INSPIRED启示录 读书笔记 - 第40章 最佳实践经验

    十大要点 1.产品管理的职责:许多产品经理将大把的时间浪费在与产品管理无关的工作上 2.用户体验:对于大多数软件产品来说,用户体验就是产品的生命 3.机会评估:用方便快捷的机会评估方法取代过时的市场需 ...

  6. Ubuntu 使用国内apt源

    编辑/etc/apt/source-list deb http://cn.archive.ubuntu.com/ubuntu/ trusty main restricted universe mult ...

  7. JMeter学习(十一)属性和变量

    一.Jmeter中的属性: 1.JMeter属性统一定义在jmeter.properties文件中,我们可以在该文件中添加自定义的属性 2.JMeter属性在测试脚本的任何地方都是可见的(全局),通常 ...

  8. 网络安全-跨站脚本攻击XSS(Cross-Site Scripting)

    一.XSS攻击简介 作为一种HTML注入攻击,XSS攻击的核心思想就是在HTML页面中注入恶意代码,而XSS采用的注入方式是非常巧妙的. 在XSS攻击中,一般有三个角色参与:攻击者.目标服务器.受害者 ...

  9. POJ 1635 Subway tree systems (树的最小表示法)

    题意:一串01序列,从一个点开始,0表示去下一个点,1表示回到上一个点,最后回到起点,遍历这棵树时每条边当且仅当走2次(来回) 给出两串序列,判断是否是同一棵树的不同遍历方式 题解:我们把每一个节点下 ...

  10. 手机APP和微信小程序能否取代域名?

    有人说现在App是主流,手机上装几个App就可以了,以后域名的重要性会越来越低,直至App完全取代域名的域名无用论.真的是这样吗? 关于这个话题已经有很多先人前辈探讨过,这次誉名网从另外一个角度给各位 ...