集合
集合是一种容器,可以用来存储多个对象数据.
数组和集合区别:
- 数组长度固定不能更改,不利于数据增删,集合长度可变
- 数组提供的方法和属性较少,不方便增删改查操作,集合提供了更加丰富的API
- 数组存储数据的单一,即有序的,可重复的。集合有很多种类型,分别有不同的存储特点。
- 数组中可以存储基本数据类型值,也可以存储对象,而集合中只能存储对象
集合主要分为两大系列:Collection和Map,Collection 表示一组对象,Map表示一组映射关系或键值对。
Collection集合
Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。
Collection接口相关方法
1、添加元素
(1)add(E obj):添加元素对象到当前集合中
(2)addAll(Collection<? extends E> other):添加other集合中的所有元素对象到当前集合中,即this = this ∪ other
2、删除元素
(1) boolean remove(Object obj) :从当前集合中删除第一个找到的与obj对象equals返回true的元素。
(2)boolean removeAll(Collection<?> coll):从当前集合中删除所有与coll集合中相同的元素。即this = this - this ∩ coll
(3)boolean retainAll(Collection<?> coll):从当前集合中删除两个集合中不同的元素,使得当前集合仅保留与c集合中的元素相同的元素,即当前集合中仅保留两个集合的交集;
(4) void clear(); 清空集合
3、其他操作
(1)int size():获取当前集合中实际存储的元素个数
(2)boolean isEmpty():判断当前集合是否为空集合。
(3)boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素。
(4)boolean containsAll(Collection<?> c):判断c集合中的元素是否在当前集合中都存在。即c集合是否是当前集合的“子集”。
(5)Object[] toArray():返回包含当前集合中所有元素的数组
Iterator迭代器
1、Iterator接口
Iterator
主要用于迭代访问(即遍历)Collection
中的元素,因此Iterator
对象也被称为迭代器。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作
public Iterator iterator()
: 获取集合对应的迭代器
迭代:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。
Iterator接口的常用方法如下:
public E next()
:返回迭代的下一个元素。public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。public void remove()
:通过迭代器删除元素
tips::在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。
2、迭代器的实现原理
在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,指向第一个元素,当第一次调用迭代器的next方法时,返回第一个元素,然后迭代器的索引会向后移动一位,指向第二个元素,当再次调用next方法时,返回第二个元素,然后迭代器的索引会再向后移动一位,指向第三个元素,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。
3、增强for循环
增强for循环(也称foreach循环)是JDK1.5出现的一个新特性,主要用来遍历数组和集合的。
语法格式:
for(元素的数据类型 元素名 : Collection集合or数组){
//循环体
}
//这里元素名就是一个临时变量,自己命名就可以,表示每个元素
遍历数组
通常只进行遍历元素,不要在遍历的过程中对数组元素进行修改。
public class NBForDemo1 {
public static void main(String[] args) {
int[] arr = {3,5,6,87};
//使用增强for遍历数组
for(int a : arr){//a代表数组中的每个元素
System.out.println(a);
}
//---------------------------------
String[] names = {"张三","李四","王五"};
for (String name : names) {//name代表数组中的每个元素
System.out.println(name);
}
}
}
遍历集合
通常只进行遍历元素,不要在遍历的过程中对集合元素进行增加、删除、替换操作。
public class NBFor {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("王安石");
coll.add("李白");
coll.add("杜甫");
//使用增强for遍历
for(String s : coll){//这里的s代表集合中的每个元素
System.out.println(s);
}
}
}
java.lang.Iterable接口
Java5中增加了java.lang.Iterable接口,实际上实现了此接口的类对象就可以成为 foreach
语句的目标。
对于Collection集合来说,foreach循环本质上就是使用Iterator迭代器来完成元素的遍历的。(反编译源码或debug调试可以证实,遍历数组本质是普通for循环)
迭代器的fail-fast机制
public class TestForeach {
public static void main(String[] args) {
Collection<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("atguigu");
list.add("world");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
list.remove(iterator.next());//ConcurrentModificationException
}
}
}
如果在Iterator、ListIterator迭代器创建后的任意时间从结构上修改了集合(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。
这样设计是因为,迭代器代表集合中某个元素的位置,内部会存储某些能够代表该位置的信息。当集合发生改变时,该信息的含义可能会发生变化,这时操作迭代器就可能会造成不可预料的事情。因此,果断抛异常阻止,是最好的方法。这就是Iterator迭代器的快速失败(fail-fast)机制。
迭代器如何实现快速失败(fail-fast)机制的呢?
- 在ArrayList等集合类中都有一个modCount变量。它用来记录集合的结构被修改的次数。
- 当我们给集合添加和删除操作时,会导致modCount++。
- 然后当我们用Iterator迭代器遍历集合时,创建集合迭代器的对象时,用一个变量记录当前集合的modCount。例如:
int expectedModCount = modCount;
,并且在迭代器每次next()迭代元素时,都要检查expectedModCount != modCount
,如果不相等了,那么说明你调用了Iterator迭代器以外的Collection的add,remove等方法,修改了集合的结构,使得modCount++,值变了,就会抛出ConcurrentModificationException。
Arraylist的Itr迭代器:
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount = modCount;//在创建迭代器时,expectedModCount初始化为当前集合的modCount的值
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();//校验expectedModCount与modCount是否相等
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)//校验expectedModCount与modCount是否相等
throw new ConcurrentModificationException();//不相等,抛异常
}
}
注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException
。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:*迭代器的快速失败行为应该仅用于检测 bug。*例如:
@Test
public void test02() {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("atguigu");
list.add("world");
//以下代码没有发生ConcurrentModificationException异常
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String str = iterator.next();
if("atguigu".equals(str)){
list.remove(str);
//iterator.remove();//使用迭代器的remove方法删除元素,是安全的
}
}
}