Java集合(上) Java ConcurrentModificationException

基础知识:

什么是集合?

集合是一个容器。把多个对象放入容器中。有一个水杯,你可以选择把水不断往里装,也可以选择装牛奶。但是不能两种不同的东西混合装一个杯子。集合这个容器里装的一定是同一类型的东西。(引用类型,不能是基本类型)

看到这个介绍,我们可能想到数组,数组要求的也是里面必须存放的是一种数据类型的结构。

数组和集合的区别呢?

数组大小是固定的,集合的大小理论上是不限定。

数组里的元素可以是基本类型,也可以是引用类型。集合只能放引用类型。

下图是集合家族的主要成员们(图来自百度)

由图可见,Collection接口Map接口是两个老大。Collection接口下面又生出来了Set接口(无序),List接口(有序),queue接口。Map接口保存的是有映射关系的数据。Map里的子类都有一个共同的特征就是里面数据都是key-value.举例,语文-80,数学-78,科目是不能重复的,分数是可以重复的,所以,Map里的key不能重复,value可重复。需要查分数(value),就通过科目(Key)来取。

具体实现类,常用的有ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap等等,主要分为以下三类

从上图可知,Set集合是无序的,只能根据集合里的元素本身访问。

List集合是有序的,可以通过索引访问。

Map集合可通过每个元素的Key访问value.

下面列出了Collection接口下方法

 大概看一眼,这些方法无非就是添加对象,移除对象,判断集合是不是空,清空容器,所以无需记忆。

有方法值的一提的是  当你需要把集合元素转成数组元素时候用Object[]  toArray() ,有个陷阱:注意注释部分,数组不能直接(String[])这样强制将数组变量转换,只有在使用使将元素转换为String,

首先看下List的这两个方法的说明:

 Object[ ] toArray() :返回按适当顺序包含列表中的所有元素的数组(从第一个元素到最后一个元素)。

 <T> T[ ]  toArray(T[] a) :返回按适当顺序(从第一个元素到最后一个元素)包含列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型

 Collection<String> arr = new ArrayList<String>();

       arr.add("a");

       arr.add("b");

       arr.add("b");//可以添加重复的对象
       // String[] str = (String[])arr.toArray();//error

       Object[] obj = arr.toArray();//第一种方法

       String[] obj1 = arr.toArray(new String[3]);//第二种方法

Db.query()第二个是多个不确定的参数,多个参数可以被作为数组传进来。List集合转数组举例

/**
     * 封装预处理参数解析并执行查询
     * @param sqlId
     * @param param
     * @return
     */
    public <T> List<T> query(String sqlId, Map<String, Object> param){
        LinkedList<Object> paramValue = new LinkedList<Object>();
        String sql = getSqlByBeetl(sqlId, param, paramValue);
        return Db.query(sql, paramValue.toArray());
    }

补充下getSqlByBeetl如果要sql里需要Map的话,如下参考:

/**

     * 验证编码是否存在
     * @param operatorid
     * @param type
     * @return boolean
     * 描述:新增角色组时operatorid为空,修改角色组时operatorid传值
     */
    public boolean valiQbgjxwcqkxxno(String qbgjxwcqkxxno, String type){
        Map<String, Object> param = new HashMap<String, Object>();
        param.put("column", Jxwc.column_qbggyzfqkxxno);
        param.put("table", Jxwc.table_name);
        String sql = getSqlByBeetl(Jxwc.sqlId_select, param);
        List<Jxwc> list = Jxwc.dao.find(sql,qbgjxwcqkxxno);
        int size = list.size();
        if("add".equals(type)){
            if(size == 0){
                return true;
            }
        }else{
            if(size <= 1){
                return true;
            }
        }        
        return false;
    }

Collection接口是List,Set,Queue接口的父接口,所以Collection图方法都能操作下这三种集合。 

Itertor接口

看集合主要成员图可知Itertor接口不在图上,但是他也是集合框架的成员,但是它与Map集合系列,C

ollection集合系列不同,它主要装的是遍历Collection集合里的元素。Itertor对象也叫迭代器,依托Collection对象存在。提供遍历Collection的统一编程接口。

主要的方法:

boolean hasNext() 要是被遍历的集合还没遍历完,就返回true

Object next() 返回集合里的下一个元素

void remove() 删除上一次next()返回的元素

package Test01;
  
 
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
  
public class Test {
  
    public static void main(String[] args) {
        Collection  arr = new HashSet<>();
        arr.add("a");
        arr.add("b");
        arr.add("C");//可以添加重复的对象
        Iterator  iterator =arr.iterator();
        while(iterator.hasNext()) {
         String next =     (String) iterator.next();
         System.out.println(next);
         if(next.equals("C")) {
             iterator.remove();
         } 
         next ="修改迭代变量的值看看有没影响";    //注意下
        }
       
    System.out.println(arr.toString());
     }
}

运行完发现“注意下”的地方本想改变集合里的元素却没变。所以可得到:Iterator并不是得到集合本身的元素,而是得到元素的值而已,所以修改迭代变量的值并不会影响集合本身。

Iterator不像其他集合,没有承装对象的能力。如果他不依托集合存在,根本没存在的价值。

想删除集合元素,必须通过Iterator的remove() 删除上一次next()返回的元素,不能集合自己remove(Object)

举例,会发现异常

package Test01;
  
 
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
  
public class Test {
  
    public static void main(String[] args) {
        Collection  arr = new HashSet<>();


        arr.add("a");


        arr.add("b");


        arr.add("C");//可以添加重复的对象
        Iterator  iterator =arr.iterator();
        while(iterator.hasNext()) {
         String next =     (String) iterator.next();
         System.out.println(next);
         if(next.equals("b")) {
             arr.remove(next);
         } 
         next ="修改迭代变量的值看看有没影响";
        }
       
    System.out.println(arr.toString());
     }
}

异常原因是 迭代器采用快速-失败原则(fast-fail),一旦迭代过程中发现Collection集合中元素被修改,就引发异常。偶尔发现,如果刚刚代码改成

if(next.equals("C")) {

             arr.remove(next);
         }

也不会异常,----只有删除特定元素才会这样,但是不该冒险去做。

foreach也能迭代访问集合,但是注意,他得到的也不是集合元素本身,系统只是把集合元素的值赋给迭代变量而已,也同样同上会引发Java ConcurrentModificationException异常。

如下代码(错误示范)

package Test01;
  
 
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
  
public class Test {
  
    public static void main(String[] args) {
        Collection  arr = new HashSet<>();


        arr.add("a");


        arr.add("b");


        arr.add("C");//可以添加重复的对象
         for(Object a:arr) {
         if(a.equals("b")) {
             arr.remove(a);
         } 
         }
    }
      
}

Set集合

就像把对象随意扔进罐子里,无法记住元素的添加顺序。Set某种程度就是Collection,方法没有不同,只是行为稍微不同,(不允许重复元素),如果一定要往里加两个相同元素,添加失败add()返回false; 

上面的Set的一些共同点,Hashset,TreeSet,EunmSet三个实现类还各有特色。

依次介绍下

Hashset 

判断Hashset 集合里的两个对象相等,过两关,equal()比较相等,对象的hashcode()也相等

为什么还得比较对象的hashcode()?

Hashset 集合收进一个对象时,会调用对象的hashcode()得到其Hashcode值来决定他的存储位置。所以,即使是equal()比较相等的两个对象,hashcode不同,存放在hashset里的位置不同,依然能把这两个对象添加成功。

注意:把对象装进hashset时,如果要重写equals方法,也得重写hashcode 方法,因为equals()相等的两对象hashcode 也是相同的。

提问:hashcode()对hashset是很重要的吗?

答:hash算法是快速查找被检索的对象。通过对象的hashcode定位集合里的对象的存储位置。定位该元素。对比下,数组是存储一组元素最快的数组结构,数组通过索引找到它的组员,通过索引能计算元素在内存里的存储位置。

但是为嘛有了数组,还用hashset呢?数组也有局限性,索引是连续的,而且长度不可变。

hashset有了hashcode,所以能快速定位对象位置,而且任意增加对象。

重写hashcode() 注意java.lang.Object中对hashCode的约定:

两个对象通过equals()比较相等时,他们的hashcode 也应该是一样的。

程序运行过程中,同一个对象多次调用hashcode方法返回应该是一样的。

如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法不一定会生成不同的整数结果。但是,为不相等的对象生成不同整数结果可以提高哈希表的性能。 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。

 

向hashset里添加了一个可变对象后时,要注意:如果后面的程序修改了这个可变对象的实例变量时,可能会导致他与集合里的其他元素相同,即两个对象equals返回true,hashcode也相同。导致hashSet不能正确操作那些元素。

补充了解下,可变对象创建后,对象的属性值可能会变,也就是说,创建后对象的hash值可能会改变。

举例:对象MutableKey的键在创建时变量 i=10 j=20,哈希值是1291。然后我们改变实例的变量值,该对象的键 i 和 j 从10和20分别改变成30和40。现在Key的哈希值已经变成1931。显然,这个对象的键在创建后发生了改变。所以类MutableKey是可变的。

下面代码是hashset里添加了一个可变对象例子,

可看出,hashset已经添加了几个成员后,修改一个成员的实例变量,会得到里面有相同的成员,因此是不对的。

但是,对最后一行,不能准确访问成员这个。有点疑问,待解决。

package Test01;

import java.util.HashSet;
import java.util.Iterator;

class mutClass{
    public int count;
    public  mutClass(int count) {
        this.count =count;
    }
    public  boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }
        if(obj != null && obj.getClass() == mutClass.class) {
            mutClass m =(mutClass) obj;
            return this.count == m.count;
        }
        return false;
    }
    public  int hashcode() {
        return this.count;
    }
    public String toString() {
        return "试试mutClass[count=" + count + "]";
    }
    
}
public class TestHashSet {
   @SuppressWarnings("unchecked")
public static void main(String[] args){
       HashSet testHashSet =new HashSet();
       
       mutClass a = new mutClass(3);
       mutClass b = new mutClass(1);
       mutClass c = new mutClass(-9);
       mutClass d = new mutClass(9);
       testHashSet.add(a);
       testHashSet.add(b);
       testHashSet.add(c);
       testHashSet.add(d);
        System.out.println("第一次"+testHashSet);
        Iterator iterator =testHashSet.iterator();
       mutClass first = (mutClass) iterator.next();
       first.count=9;
      /* testHashSet.remove(new mutClass(3));
       testHashSet.remove(b);    //与上一行的区别
*/       System.out.println("第二次"+testHashSet);
        System.out.println(new mutClass(-9) == new mutClass(-9));
       System.out.println("第四次"+testHashSet.contains(new mutClass(-9)));
    }
}

 hashset不能保证添加成员的顺序,和自己的顺序是一样的,但是引入了一个LinkedHashSet子类,使得它能和hashset一样,靠hashcode 找到他的存储位置,又能维护添加成员的顺序,内部靠一个链表实现,迭代访问集合时有很好的性能。

猜你喜欢

转载自www.cnblogs.com/yizhizhangBlog/p/9251219.html