数据结构、集合、Collection 常用功能、迭代器
数据结构
容器:数组–>集合
顺序结构: 数组
空间是连续,有索引位置
链式结构:
单向链表
双向链表
循环链表
二叉树 有大小序
红黑树
集合
集合:集合是java中提供的一种容器,可以用来存储多个数据。
数组 --> 需要指定数据类型、数组需要指定长度
集合 --> 可以不指定数据类型、也不需要指定长度、集合中只能存储对象(不能够存储基本数据类型)
- 数组的长度是固定的。集合的长度是可变的。
- 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。
集合的分类:
单列集合 : value
双列集合 : key-value 键值对
单列集合 ( Collection 常用功能)
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。
Collection (list和set相当于子接口对一下方法包含)
List :有序(添加顺序)且可重复
ArrayList 底层是数组
LinkedList 底层是双向链表
Vector 底层是数组(被淘汰)
Set : 无序(添加顺序)且不可重复
HashSet : hash值有关
TreeSet : 大小顺序
LinkedHashSet : 有序(添加顺序)且不可重复
方法如下:
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、判断元素
(1)boolean isEmpty():判断当前集合是否为空集合。
(2)boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素。
(3)boolean containsAll(Collection<?> c):判断c集合中的元素是否在当前集合中都存在。即c集合是否是当前集合的“子集”。
4、查询
(1)int size():获取当前集合中实际存储的元素个数
(2)Object[] toArray():返回包含当前集合中所有元素的数组
5、交集
(1)boolean retainAll(Collection<?> coll):当前集合仅保留与c集合中的元素相同的元素,即当前集合中仅保留两个集合的交集,即this = this ∩ coll;
6.遍历
普通循环或者foreach或者迭代器
List集合
特点:有序且可重复
包含的工具类:ArrayList,LinkedList,Vector 都是位于Java.util包下的工具类,他们都实现了List接口
ArrayList 集合工具类
特点:
ArrayList底层是动态数组,它是基于数组的特性而演变出来的,所以遍历访问特别快,但是删除慢。ArrayList是一个非线程安全的容器,在并发场景下会造成问题;ArrayList在扩容时会增加50%的容量,且在刚创建出实例话对象时没有数组长度
LinkedList 集合工具类
特点:
LinkedList 是双向链表,所以LinkedList 的增加和删除非常的快,只需要把元素删除,把各自的指针指向新的元素即可。但是LinkedList 遍历比较慢,因为没访问一个元素才能知道下一个元素的值。LinkedList 是个线程安全的容器
Vector 集合工具类
特点:
Vector是个最早的数组容器,Vector是一个线程安全的容器,因为它的每个方法都粗暴的加上了synchronized锁,所以他的增删,遍历效率都很低。Vector在扩容时,它的效率会增加一倍
Set集合
特点:无序(添加顺序)且不可重复
**包含工具类:**HashSet,LinkedHashSet,TreeSet都是位于Java.util包下的工具类,他们都实现了Set接口
HashSet 集合工具类
特点:
数据顺序和数据hash值有关
LinkedHashSet 集合工具类
特点:
有序的不可重复
TreeSet 集合工具类
特点:(支持自动排序但是对象必须定制排序)
有大小顺序不可重复
添加的数据必须是相同类型的
需要支持对比机制
自然排序(内部比较器)
定制排序(外部比较器)
ArrayList,Vector,ArrayList源码分析
1)Vector源码分析
public Vector() {
this(10);//指定初始容量initialCapacity为10
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);//指定capacityIncrement增量为0
}
public Vector(int initialCapacity, int capacityIncrement增量为0) {
super();
//判断了形参初始容量initialCapacity的合法性
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//创建了一个Object[]类型的数组
this.elementData = new Object[initialCapacity];//默认是10
//增量,默认是0,如果是0,后面就按照2倍增加,如果不是0,后面就按照你指定的增量进行增量
this.capacityIncrement = capacityIncrement;
}
//synchronized意味着线程安全的
public synchronized boolean add(E e) {
modCount++;
//看是否需要扩容
ensureCapacityHelper(elementCount + 1);
//把新的元素存入[elementCount],存入后,elementCount元素的个数增1
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
//看是否超过了当前数组的容量
if (minCapacity - elementData.length > 0)
grow(minCapacity);//扩容
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//获取目前数组的长度
//如果capacityIncrement增量是0,新容量 = oldCapacity的2倍
//如果capacityIncrement增量是不是0,新容量 = oldCapacity + capacityIncrement增量;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
//如果按照上面计算的新容量还不够,就按照你指定的需要的最小容量来扩容minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量超过了最大数组限制,那么单独处理
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//把旧数组中的数据复制到新数组中,新数组的长度为newCapacity
elementData = Arrays.copyOf(elementData, newCapacity);
}
public boolean remove(Object o) {
return removeElement(o);
}
public synchronized boolean removeElement(Object obj) {
modCount++;
//查找obj在当前Vector中的下标
int i = indexOf(obj);
//如果i>=0,说明存在,删除[i]位置的元素
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
public int indexOf(Object o) {
return indexOf(o, 0);
}
public synchronized int indexOf(Object o, int index) {
if (o == null) {//要查找的元素是null值
for (int i = index ; i < elementCount ; i++)
if (elementData[i]==null)//如果是null值,用==null判断
return i;
} else {//要查找的元素是非null值
for (int i = index ; i < elementCount ; i++)
if (o.equals(elementData[i]))//如果是非null值,用equals判断
return i;
}
return -1;
}
public synchronized void removeElementAt(int index) {
modCount++;
//判断下标的合法性
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
//j是要移动的元素的个数
int j = elementCount - index - 1;
//如果需要移动元素,就调用System.arraycopy进行移动
if (j > 0) {
//把index+1位置以及后面的元素往前移动
//index+1的位置的元素移动到index位置,依次类推
//一共移动j个
System.arraycopy(elementData, index + 1, elementData, index, j);
}
//元素的总个数减少
elementCount--;
//将elementData[elementCount]这个位置置空,用来添加新元素,位置的元素等着被GC回收
elementData[elementCount] = null; /* to let gc do its work */
}
(2)ArrayList源码分析
a. 在实例化ArrayList对象时会创建3个属性
① 用于存储数据的数组 Object[0]
② size 记录数据的个数
③ modCount 记录当前集合的版本
b. 在第一次添加数据时,将数组容量扩充为10,在将数据添加到第一个位置
c. 在后续的添加过程中
如果数组是有空间的,就直接添加数据
如果数组已满,先扩容(原容量的1.5倍),再添加数据
JDK1.7
private static final int DEFAULT_CAPACITY = 10;//默认初始容量10
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;//数组初始化为一个空数组
}
public boolean add(E e) {
//查看当前数组是否够多存一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {//如果当前数组还是空数组
//minCapacity按照 默认初始容量和minCapacity中的的最大值处理
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//看是否需要扩容处理
ensureExplicitCapacity(minCapacity);
}
//...
JDK1.8
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//初始化为空数组
}
public boolean add(E e) {
//查看当前数组是否够多存一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
//存入新元素到[size]位置,然后size自增1
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果当前数组还是空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//那么minCapacity取DEFAULT_CAPACITY与minCapacity的最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//查看是否需要扩容
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//修改次数加1
// 如果需要的最小容量 比 当前数组的长度 大,即当前数组不够存,就扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//当前数组容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//新数组容量是旧数组容量的1.5倍
//看旧数组的1.5倍是否够
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//看旧数组的1.5倍是否超过最大数组限制
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//复制一个新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
public boolean remove(Object o) {
//先找到o在当前ArrayList的数组中的下标
//分o是否为空两种情况讨论
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {//null值用==比较
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {//非null值用equals比较
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;//修改次数加1
//需要移动的元素个数
int numMoved = size - index - 1;
//如果需要移动元素,就用System.arraycopy移动元素
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将elementData[size-1]位置置空,让GC回收空间,元素个数减少
elementData[--size] = null; // clear to let GC do its work
}
public E remove(int index) {
rangeCheck(index);//检验index是否合法
modCount++;//修改次数加1
//取出[index]位置的元素,[index]位置的元素就是要被删除的元素,用于最后返回被删除的元素
E oldValue = elementData(index);
//需要移动的元素个数
int numMoved = size - index - 1;
//如果需要移动元素,就用System.arraycopy移动元素
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将elementData[size-1]位置置空,让GC回收空间,元素个数减少
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public E set(int index, E element) {
rangeCheck(index);//检验index是否合法
//取出[index]位置的元素,[index]位置的元素就是要被替换的元素,用于最后返回被替换的元素
E oldValue = elementData(index);
//用element替换[index]位置的元素
elementData[index] = element;
return oldValue;
}
public E get(int index) {
rangeCheck(index);//检验index是否合法
return elementData(index);//返回[index]位置的元素
}
public int indexOf(Object o) {
//分为o是否为空两种情况
if (o == null) {
//从前往后找
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public int lastIndexOf(Object o) {
//分为o是否为空两种情况
if (o == null) {
//从后往前找
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
(3)LinkedList源码分析
int size = 0;
Node<E> first;//记录第一个结点的位置
Node<E> last;//记录最后一个结点的位置
private static class Node<E> {
E item;//元素数据
Node<E> next;//下一个结点
Node<E> prev;//前一个结点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
public boolean add(E e) {
linkLast(e);//默认把新元素链接到链表尾部
return true;
}
void linkLast(E e) {
final Node<E> l = last;//用l 记录原来的最后一个结点
//创建新结点
final Node<E> newNode = new Node<>(l, e, null);
//现在的新结点是最后一个结点了
last = newNode;
//如果l==null,说明原来的链表是空的
if (l == null)
//那么新结点同时也是第一个结点
first = newNode;
else
//否则把新结点链接到原来的最后一个结点的next中
l.next = newNode;
//元素个数增加
size++;
//修改次数增加
modCount++;
}
public boolean remove(Object o) {
//分o是否为空两种情况
if (o == null) {
//找到o对应的结点x
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);//删除x结点
return true;
}
}
} else {
//找到o对应的结点x
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);//删除x结点
return true;
}
}
}
return false;
}
E unlink(Node<E> x) {//x是要被删除的结点
// assert x != null;
final E element = x.item;//被删除结点的数据
final Node<E> next = x.next;//被删除结点的下一个结点
final Node<E> prev = x.prev;//被删除结点的上一个结点
//如果被删除结点的前面没有结点,说明被删除结点是第一个结点
if (prev == null) {
//那么被删除结点的下一个结点变为第一个结点
first = next;
} else {//被删除结点不是第一个结点
//被删除结点的上一个结点的next指向被删除结点的下一个结点
prev.next = next;
//断开被删除结点与上一个结点的链接
x.prev = null;//使得GC回收
}
//如果被删除结点的后面没有结点,说明被删除结点是最后一个结点
if (next == null) {
//那么被删除结点的上一个结点变为最后一个结点
last = prev;
} else {//被删除结点不是最后一个结点
//被删除结点的下一个结点的prev执行被删除结点的上一个结点
next.prev = prev;
//断开被删除结点与下一个结点的连接
x.next = null;//使得GC回收
}
//把被删除结点的数据也置空,使得GC回收
x.item = null;
//元素个数减少
size--;
//修改次数增加
modCount++;
//返回被删除结点的数据
return element;
}
迭代器
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
**定义:**迭代器(Iterator)模式,又叫做游标模式,它的含义是,提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
从定义上看,迭代器是为容器而生,它本质上就是一种遍历的算法。
简单的说,迭代器就是一个接口Iterator,实现了该接口的类就叫做可迭代类,这些类多数时候指的就是java.util包下的集合类。
迭代器主要的用法是:首先用hasNext()作为循环条件,再用next()方法得到每一个元素,最后在进行相关的操作。
集合的遍历:
-
foreach循环(不能在便利时删除修改数据)
-
迭代器 Iterator(可以在便利时删除修改数据)
-
① Iterator iter=集合对象.iterator()
-
② iter.hasNext() 判断下一个位置是否有值
-
③ iter.next() 取出下一个位置的值,游标往下移动一格
-
④ iter.remove() 删除当前游标指向的数据
-
在遍历过程中,是不允许对集合进行数据操作(添加、删除)
-
Iterator接口的常用方法如下:
public E next()
:返回迭代的下一个元素。public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。
接下来我们通过案例学习如何使用Iterator迭代集合中元素:
public class IteratorDemo { public static void main(String[] args) { // 使用多态方式 创建对象 Collection<String> coll = new ArrayList<String>(); // 添加元素到集合 coll.add("串串星人"); coll.add("吐槽星人"); coll.add("汪星人"); //遍历 //使用迭代器 遍历 每个集合对象都有自己的迭代器 Iterator<String> it = coll.iterator(); // 泛型指的是 迭代出 元素的数据类型 while(it.hasNext()){ //判断是否有迭代元素 String s = it.next();//获取迭代出的元素 System.out.println(s); } } }
tips::在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。
Map集合
Map集合是双链集合(键值对的形式存储的)
特性:
map中所有的Key值和set集合的特性一样(无序且不可重复)
map中所有的value值相当于一个Collection(可以重复,顺序看key值)
Map常用方法
1、添加操作
- V put(K key,V value)
- void putAll(Map<? extends K,? extends V> m)
2、删除
- void clear()
- V remove(Object key)
3、元素查询的操作
- V get(Object key)
- boolean containsKey(Object key)
- boolean containsValue(Object value)
- boolean isEmpty()
4、元视图操作的方法:
- Set keySet()
- Collection values()
- Set<Map.Entry<K,V>> entrySet()
5, 查
-
size()
-
isEmpty()
-
containsKey(Object key)
-
containsValue(Object value)
-
get(Object key)
6, 遍历
单独遍历
-
key
-
Set keySet() 拿到map中所有的key值
-
value
-
Collection values() 拿到所有的value值
整体遍历
-
entrySet() 拿到所有的键值对
-
注意: 一个键值对的类型是Map.Entry
-
Map.Entry
-
getKey()
-
getValue()
HashMap : key值的顺序和key值的hash值有关
LinkedHashMap : key值有序的(添加顺序)
TreeMap : key值有大小顺序的
key值要求相同类型数据,key值要求有排序机制
Hashtable : key值的顺序和key值的hash值有关
Properties : Hashtable的子类 //读取配置文件
HashMap
a. 在实例化HashMap的对象时
table =null 存储数据的数组
size=0
modCount=0
loadFactor=0.75 负载因子
threshold=0 临界值 (数组的长度*负载因子)
b. 添加数据
第一次添加数据,table的容量设置为16,临界值计算得出12,并且将数据添加到数组中了(位置是根据key值的hash值和数组的长度算出来的)
知识点:table的数据类型 (对象数组)
HashMap$Node
hash : key值的hash值(经过运算)
key : key值
value : value值
next : null (干啥的?) 单向链表
后续添加数据:添加完数据判断当前数据个数是否超过临界值,
如果没有超过,就完事
如果超过了,就扩容(扩容机制是原容量的2倍,临界值也是原来的2倍)
每次扩容数组都会重新计算数据的位置
添加数据的原理:
① 计算key值的hash值
② 根据数组的长度和key值的hash值计算一个索引值
③ 判断该索引位置是否有值
没有值
就直接将数据封装成Node对象,存储在计算得到的这个索引位置处
有值
a.在添加数据的时候,hash值是一样的,导致索引位置计算的是相同的,
b. 在添加数据的时候,hash值是不一样,也有可能导致计算出来的索引位置是一样的,
深入的判断是否是同一个数据
hash值是否一样,如果hash值一样,会判断地址是否是相同的,如果地址不一样
还会做equals的判断
在判断的过程中,发现有相同数据,直接覆盖
在判断的过程中,没有发现相同数据,则进行添加操作(按照链表结构添加,按照红黑树结构添加)
HashMap的底层是如何实现:
数组+链表+红黑树
数组中的链表在链表长度到达8,并且数组的长度大于64,链表会变为红黑树(提高效率)
HashMap的容量原理
在第一次添加之前,数组容量设置为16,临界值为12
在后续添加的过程中,如果超过临界值了,就会进行扩容,扩容机制是原容量的2倍,
扩容之后,所有数据重新计算位置
HashMap的添加数据流程
源码分析:
a. 在实例化HashMap的对象时
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
b. 添加数据
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//计算key的hash值(实际是key的hash值经过了一个算法)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//添加数据的方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了一部分局部变量
//判断目前的table中是否有空间,如果没有空间,就会进入到if中,执行resize方法
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//tab和table的地址是相同的,指向的是同一个数组空间
//(n - 1) & hash 是根据key的hash值和数组的长度计算出来的索引位置
// 计算出来的索引位置,会判断数组的该位置是否有值
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else if(){}
else{
//循环的将当前链表中的数据依次拿出来和当前数据对比是否是相同的数据
//一旦找到相同的了,就直接覆盖
//如果整个链表都判断过了,没有相同的数据,将当前数据连接上去
}
}
第一次添加的扩容方法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//拿到存储数据的数组 table=null
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//以上四个变量都是0
if (oldCap > 0) {}
else if (oldThr > 0){}
else{
//将新容量设置为16、新的临界值计算得出是12
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
threshold = newThr;//将临界值属性设置为12
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;//将table初始化容量为16
return newTab;//将数组的地址返回
}
HashMap是如何判断两个key值是否重复
a. hash值(hashCode())
b. 地址+equals
LinkedHashMap
key值是有序(链表),数据的保存方式和HashMap是一样
TreeMap
key值是有大小顺序(key值的类型必须是同一类型,必须有对比机制)
TreeMap是如何判断两个key值是否重复
compareTo或者compare方法是否返回0判断
底层 版本 线程安全 效率 null值和null键
HashMap 数组+链表+红黑树 JDK1.2 线程不安全 效率较高 允许
Hashtable 数组+链表 JDK1.0 线程安全 效率较低 不允许
Map总结:
Set集合就是借用了Map集合的key值
HashSet–>HashMap
LinkedHashSet–>LinkedHashMap
TreeSet–>TreeMap
map中:如果key数据重复了,value值会替换,key值是不变
set中:如果数据重复了,添加失败
使用Iterator迭代器删除元素
java.util.Iterator迭代器中有一个方法:
void remove() ;
那么,既然Collection已经有remove(xx)方法了,为什么Iterator迭代器还要提供删除方法呢?
因为Collection的remove方法,无法根据条件删除。
例如:要删除以下集合元素中,名字是三个字的人名
@Test
public void test02(){
Collection<String> coll = new ArrayList<>();
coll.add("陈琦");
coll.add("李晨");
coll.add("邓超");
coll.add("黄晓明");
//删除名字有三个字的
// coll.remove(o)//无法编写
Iterator<String> iterator = coll.iterator();
while(iterator.hasNext()){
String element = iterator.next();
if(element.length()==3){
// coll.remove(element);//错误的
iterator.remove();
}
}
System.out.println(coll);
}
注意:不要在使用Iterator迭代器进行迭代时,调用Collection的remove(xx)方法,否则会报异常java.util.ConcurrentModificationException,或出现不确定行为。