前段时间,在做一个商品的分类,分类有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. ==、equals与hashCode

    ==  首先,得说明java数据类型分为基本数据类型和引用数据类型, 基本数据类型有8种: 浮点型:float(4 byte), double(8 byte) 整型:byte(1 byte), sho ...

  2. Vue.js学习笔记 第八篇 组件

    全局注册组件 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <ti ...

  3. Python的format函数

    Python的format函数 自python2.6开始,新增了一种格式化字符串的函数str.format(),此函数可以快速处理各种字符串. 语法 它通过{}和:来代替%. 请看下面的示例,基本上总 ...

  4. Go 语言基础知识

    0. Go语言书单 1. 文本注释 // 单行注释 /* */ 多行注释 2. 变量赋值 = 变量赋值 := 声明变量并赋值 3. 变量定义 var name string var age int v ...

  5. Bootstrap3全局CSS样式

    目录 1. 概览 2. 栅栏系统 3. 文本 4. 列表 5. 代码 6. 表格 7. 表单 7.1 基本实例 7.2 内联表单 7.3 水平排列的表单 8. 按钮 9. 图片 10. 辅助类 10. ...

  6. CentOs linux安装SVN服务

    SVN服务器有2种运行方式:1.独立服务器(例如:svn://xxx.com/xxx):2.借助apache   (例如:http://svn.xxx.com/xxx):为了不依赖apache,我选择 ...

  7. 暑假爆零欢乐赛SRM08题解

    这真的是披着CF外衣的OI赛制?我怎么觉得这是披着部分分外衣的CF?果然每逢cf赛制必掉rating,还是得%%%cyc橙名爷++rp.. A题就是找一找序列里有没有两个连在一起的0或1,并且不能向两 ...

  8. Ceph简介

    最近偶尔接触到云计算,开始对云计算感兴趣,希望能够早日加入这个计算机领域的第三次革命中去. 估计这次革命要持续二十年,也就是这辈子一直干云计算都没问题. 先了解一下Ceph吧.本博文主要是根据文献[1 ...

  9. 内网批量测试登录机器工具,并且dir 目标机器c盘

    // Ipc.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <stdio.h> #include <w ...

  10. Spring初学之xml实现AOP前置通知、后置通知、返回通知、异常通知等

    实现两个整数的加减乘除,在每个方法执行前后打印日志. ArithmeticCalculator.java: package spring.aop.impl.xml; public interface ...