1、起因
集合使用迭代器方式进行遍历,说"迭代过程中不建议对集合结构进行破坏,否则会报异常"
(迭代过程中,集合对象调用add() / remove()都会破坏集合结构,会报异常。如果一定要迭代过程中调用remove(),建议是调用迭代器的remove() )
关于"迭代器和集合的关系",给出的说法是:创建迭代器等同给集合拍了一个快照
我的理解:快照就是集合的副本,是一个独立的存在了。所以迭代器参照快照去遍历应该不受修改集合的影响,可是不然
对于快照这一说法并不理解,故本篇研究集合和快照之间是怎么联系的
2、代码演示,逐步看问题的产生
2.1、迭代器简单运用
//一般情况下使用迭代器进行遍历集合是这样的
//集合实例化
ArrayList list = new ArrayList<>();
//往集合中存集合元素
list.add("abc");
list.add("def");
list.add("ghi");
//此时集合使用迭代器方式进行遍历:集合对象去点iterator()获取迭代器
Iterator it = list.iterator();
//迭代器对象调用hasNext(),发现集合中存在下一个集合元素则返回true
while(it.hasNext()){
//while(true)证明集合中有元素,就可以调用next()拿出集合元素
String str=(String)it.next();
System.out.println(str);
}
基础的使用迭代器去遍历集合就是这样。再小结一下步骤就是
集合有遍历的需求,选择使用迭代器方式去遍历集合,就
1、集合对象点iterator()获得迭代器对象
2、迭代器对象点hasNext()判断集合中是否存在下一个元素,有则调用next()将元素取出。循环至没有下一个集合元素则说明已到达了集合末尾,迭代结束,停止遍历元素
2.2、迭代器使用过程中遇到的问题
情况一:迭代器声明的位置
如上,正常/一般是一个有集合元素的集合,有迭代的需求再使用迭代器去迭代。对比下面则是一个空集合,空集合后面先创建迭代器,再往集合中增加元素再进行迭代
按照"快照是独立个体存在"的思路,此时迭代器记录的是一个空集合,那是while(flase),那就无事发生
实测
在next()这行报异常
解释是:当集合结构发生了改变,可迭代器没有重新获取时,调用next()时就会报java.util.ConcurrentModificationException异常(并发修改异常)
(创建迭代器后就不应该修改集合结构了)
情况二:迭代过程中,集合调用add()
package normal.com.test;
public class CollectionTest03 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
//迭代器
Iterator i = c.iterator();
//迭代取出集合元素
while (i.hasNext()){
Object o=i.next();
c.add(14);
System.out.println(o);
}
}
}
报异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at normal.com.test.CollectionTest03.main(CollectionTest03.java:20)
1
Process finished with exit code 1
笼统解释:"集合结构发生了变化导致迭代报异常"
情况三: 迭代过程中,集合调用remove()
package normal.com.test;
public class CollectionTest03 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
//迭代器
Iterator i = c.iterator();
//迭代取出集合元素
while (i.hasNext()){
Object o=i.next();
c.remove(2);
System.out.println(o);
}
}
}
报异常
1
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at normal.com.test.CollectionTest03.main(CollectionTest03.java:20)
Process finished with exit code 1
笼统解释:"集合结构发生了变化导致迭代报异常"
下面讲联想到的其他情况
情况四:集合的基本操作增删改查,那是不是集合调用改()、查()也会破坏集合结构
目前是会的,不过我更关心既然"迭代过程中,集合的remove()不能用,但是可以用迭代器的remove()",那是不是集合进行增删改查,也可以
不用集合的add(),用迭代器的add()?
不用集合的remove(),用迭代器的remove() ->已证实
不用集合的改(),用迭代器的改()?
集合现在在用迭代器进行遍历所以没有什么查()
情况五:迭代器的remove()不管用
乌龙事件。原本认为的是:拿一个删一个,那跟着的打印语句自然打印不出来。可是拿出来的赋值给了o,删除是删除集合上的元素,后面打印的是o,所以一场误会
3、研究迭代器
3.1、先看继承实现结构图,了解迭代器由来
迭代器是Collection集合的通用迭代方式,所以画出Collection这一块足矣
1、可见ArrayList类实现List接口、List接口继承Collection接口、Collection接口继承Iterable接口。iterator()是在Iterable接口中声明的,所以iterator()就是这么一层层下来的
2、接口中的方法都是抽象方法,抽象方法没有方法体。所以看iterator()源码要在ArrayList中看如何实现iterator()
3、发现一个接口,和iterator()同名的Iterator接口
package java.util; //jdk8
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
//省略方法体
}
default void forEachRemaining(Consumer<? super E> action) {
//省略方法体
}
}
1、iterator()方法和Iterator接口,名相同;接口Iterator中确实无iterator();那是不是
iterator()写在Iterator接口中更好?
接口不能实例化,自然"iterator()作为Iterator接口的构造方法存在"这一点就不合适了
那iterator()不做构造方法(Iterator()),做一个普通方法可以写在Iterator接口中吗?
可以这么写,但看源码知道这么不推荐,继续看原因
2、Iterator接口中有hasNext()、next()、remove()
迭代器对象也常调用这三个方法,那想必iterator()和Iterator接口间必然有某种关系存在
关于这个Iterator接口的讨论到此为止,后面会再说的
3.2、看ArrayList中iterator()怎么实现的
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess,
Cloneable, java.io.Serializable{ //ArrayList类
//iterator()的实现
public Iterator<E> iterator() {
return new Itr(); //return 实例化Itr
}
} //ArrayList类结束
iterator()的实现非常简单,返回一个Itr实例
Itr是什么类? 是ArrayList中的内部类
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess,
Cloneable, java.io.Serializable{ //ArrayList类
public Iterator<E> iterator() {
return new Itr();
}
//Itr是ArrayList的私有内部类;实现了接口Iterator(iterator()和Iterator接口有联系了)
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
//上面四个参数很重要,但是目前知道有这么四个参数即可
Itr() {}
//Itr类的无参构造
//new Itr()是实例化Itr类。我们知道无参构造会缺省赋值,cursor属性会赋默认值,
//int类型故赋值0 (其他参数再说)
//所以iterator()的实现很简单,就是返回一个Itr实例。这就完了?继续看
//因为实现了接口Iterator,所以这个内部类Itr实现了接口Iterator的hasNext()、next()、
//remove()、forEachRemaining()
//这四个方法后面再细讲,所以下面摘录是省略方法体了
public boolean hasNext() {
//省略方法体
}
public E next() {
//省略方法体
}
public void remove() {
//省略方法体
}
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
//省略方法体
}
//集合对象调用iterator()创建了迭代器对象,而迭代器对象迭代过程中使用的
//hasNext()、next()、remove()就是Itr类内实现接口Iterator的
//这才是iterator()和Iterator接口之间的联系
//集合的增删改()都不能用,都用迭代器的增删改()?
//现在可以回答了。集合的增删改()都不能用,因为"会破坏集合结构";迭代器关于增删改
//只提供了remove()
//内部类Itr中还有这么一个自己独有的方法
final void checkForComodification() {
//省略方法体
}
} //Itr类结束
} //ArrayList类结束
继续看,看3.3的完整逻辑代码
插曲
如果你查找源码时,搜索iterator()还会发现:居然iterator()还有一个实现
public Iterator<E> iterator() {
return listIterator();
}
实际上面这个方法是ArrayList的私有内部类SubList内的
private class SubList extends AbstractList<E> implements RandomAccess {
public Iterator<E> iterator() {
return listIterator();
}
//关于这个iterator(),后面会说cursor的赋初值和此有关,我不认可这个说法,后面会拉出该说法
3.3、细看iterator()的实现逻辑
细看ArrayList的内部类Itr实现接口Iterator的那些方法
//1、先解释Ir的四个参数:cursor、lastRet、expectedModCount、modCount
cursor是Itr的属性,翻译为"光标"。cursor"光标",记录啥呢?记录索引值
实际叫作"下一个元素的下标"更合适
lastRet,当前操作的位置,指针的位置,或者记作"当前下标"
expectedModCount,"期望的模值",快照时记录的集合的模值
modCount,"模值",集合的模值 //这个模值可以记作版本号,也就是集合修改会影响版本号
//迭代过程中,集合和快照保持一致就是expectedModCount = modCount作比较返回true
//报异常说明版本号的检查返回false了
public boolean hasNext() {
return cursor != size; } //size是ArrayList的属性,表示集合的大小
hasNext()见名知义是判断是否有下一个元素,实际内部逻辑很简单,就判断cursor和size值是否相等
cursor的赋初值我认为是在new Itr()时赋值的,int类型故赋值0
cursor后续值+1是在next()中+1 (cursor+1就是在更新下标)
迭代器刚刚创建的时候是一个未指向任何元素的状态,可以想象为指向集合"之前的位置"。迭代器
的索引位于第一个元素之前的位置,而这个"之前的位置下标"可以看作是-1
因此与指针一开始指向下标-1相比,cursor拿到下标0自然是拿到"下一个元素的下标"
public E next() {
checkForComodification(); //跳最后看这个方法什么功能
//得知该方法就是用于判断集合和快照是否一致的,版本号一致则集合结构和快照记录的一致
//不一致则报异常
//注意报异常的是next()
//不看源码,想当然一看迭代过程中最先调用hasNext()、见名知义知道这是判断集合中是否有下一个元素
//就会以为是hasNext()报的异常。这也是要注意的点
int i = cursor; //拿到cursor自然要取cursor对应的集合元素了
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1; //先下标+1
return (E) elementData[lastRet = i]; //后取得下标对应的元素
}
//只有当我们调用 next()后,迭代器索引才正式指向第一个元素并返回该元素
//注意next()是先cursor+1后再取集合元素的
//一开始只知道next()用于取出下一个集合元素,现在知道了指针更新是next()内cursor+1干的
//cursor+1是更新下标,理解为循环的更新表达式,这样才是合理地进入下一次循环
//next()简化后就是
public Object next() {
int i = cursor;
cursor = i + 1;
return elementData[lastRet = i];}
解释了hasNext()和next()的逻辑,其实很多问题可以说明白了
//lastRet就是迭代器的指针位置(和lastRet=-1对上了)
//cursor是要取的下一个元素的下标/索引
//一般的遍历是指针和长度作比较。比如一个大小3的数组,指针指向下标0,0和3比较返回true则取出
//0对应的数组元素、再更新指针进入下一次比较(1和3比...2和3比...)
//迭代器的遍历则是cursor取下一个元素的下标、cursor和长度作比较,返回true则将指针更新
//(cursor赋值给指针,再指针取对应下标的元素、cursor再更新、进入下一次比较
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
//看为什么迭代器的remove()可以用不会影响到版本号不一致
try {
ArrayList.this.remove(lastRet); //集合的remove(int index)
cursor = lastRet;
lastRet = -1; //这两行我不懂
expectedModCount = modCount; //所以迭代器的remove()可以用就在于
//保持了版本号一致
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//迭代器的remove()内"expectedModCount = modCount; "使得版本号检查能返回true
//那集合add()、remove()肯定没有这行代码,那有修改版本号的代码吗?最后看
//这个没用就不去研究了
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
//判断版本号是否一致:版本号的比较就是expectedModCount和modCount的比较
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
看集合的add()、remov()有没有涉及版本号修改的代码存在
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public E remove(int index) {
rangeCheck(index);
modCount++; //版本号自增了
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
可见只要对ArrayList作了加或删操作都会增加modCount版本号。这就导致在迭代期间检查modCount
和迭代器持有的expectedModCount两者是不相等,就抛出异常了
所以集合和快照保是否一致就看版本号是否一致
关于cursor的初值,有下面这一说法
我们知道迭代判定是否往下继续执行的条件判断是while(hasNext()),而hasNext()关键看cursor这个参数,那这个参数是哪里赋值的呢?
iterator(){ return listIterator(); }
list调用了iterator(),返回值是一个迭代器;iterator()内又调用了listIterator()
listIterator(){ return ListIterator(0); }
ListIterator(final int index){ return new ListItr(index); }
ListItr(int index){ cursor=index; }
经查,这个一层层下来代码很复杂,作者是简化过的
public class ArrayList<E> extends AbstractList<E> implements List<E>,
andomAccess, Cloneable, java.io.Serializable{ //ArrayList类
private class SubList extends AbstractList<E> implements RandomAccess {
//ArrayList类的内部类SubList
public Iterator<E> iterator() {
return listIterator(); //iterator()内返回listIterator()
}
public ListIterator<E> listIterator() {
return new ListItr(0);
//listIterator()内返回ListItr实例 //该方法内比较复杂,简化后是
}
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index; //cursor在这里初始化的,赋初值0
}
对于cursor是这样初始化的我不太认可,毕竟我觉得就是return new Itr();这里赋初值的
到从,研究整理结束,后面属于拓展内容
4、拓展
摘录整理过程中遇到的其他知识点,不深入研究,故了解即可
4.1、关于next()的调用
1、迭代器对象点next()拿到的都是Obejct类型
有数据类型要求再向下转型;如果集合存放的对象不是同一数据类型的则不可以强制都转为同一数据类型向下转型
2、迭代器创建后,必须先while(迭代器对象.hasNext())判断,再迭代器对象.next()获取
直接调用迭代器对象点next()会抛出NoSuchElementException异常。所以在调用iterator.next()前必须要调用it.hasNext()进行检测。若不调用则下一条记录无效
3、迭代过程结束了,执行迭代器对象点next()
如果集合中已经没有元素了,迭代结束了,还继续使用迭代器的next()将会发生java.util.NoSuchElementException没有集合元素的错误
一旦指针移到了最后一位,理论上便回不去了,所以说迭代器是一次性的
while(.hasNext()){
next(); }
//迭代结束后,再执行获取集合元素
next();
迭代后再调用next(),此时应该cursor==size、lastRet==size-1
看next()源码
public E next() {
checkForComodification(); //版本号应该还是一致的,所以这里不报异常
int i = cursor;
if (i >= size) //到这里就声明了这个NoSuchElementException异常
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
所以一般迭代过程结束了,再用迭代器要实例化一个新的拿去用
下面依旧在提醒:1是取出来都是Obejct类型,向下转型要注意;2、迭代结束后调用next()的问题
public class Student {
//成员属性
private String name;
private int age;
//构造方法
public Student(String name, int age) {
this.name = name;
this.age = age; }
//get和set方法
public String getName() {
return name; }
public void setName(String name) {
this.name = name; }
public int getAge() {
return age; }
public void setAge(int age) {
this.age = age; }
//重写toString(),实现输出对象
@Override
public String toString() {
return "Student{"+"name='" + name + ''' +", age=" + age +'}'; }}
public class IteratorExercise {
public static void main(String[] args) {
//初始化一个存放Student类对象的集合对象
Collection a = new ArrayList<>();
a.add(new Student("zs", 24));
a.add(new Student("lisi", 25));
a.add(new Student("wangwu", 26));
Iterator iterator = a.iterator();
while(iterator.hasNext()) {
Student stu = iterator.next();
System.out.println(stu.getName() + " -- " + stu.getAge());
//以下做法是错误的,因为每调用一次next()方法
//cursor都会后移一位,就指向了下一个对象元素 //System.out.print(iterator.next().getName() + " -- " + iterator.next().getAge()); } }}
是说最后的打印是前面对的,后面再iterator.next()就不对了,是这个意思
//不指定存储的数据类,什么都存,就是 Object类型
Collection coll = new ArrayList();
coll.add("abc");
coll.add("aabbcc");
coll.add("shitcast");
//获取迭代器,也不能加<>
Iterator it = coll.iterator();
//由于元素被存放进集合后全部被提升为Object类型
//当需要使用子类对象特有方法时,需要向下转型
while (it.hasNext()) {
String str = (String) it.next();
System.out.println(str.length());}
//注意:如果集合中存放的是多个对象,这时进行向下转型会发生类型转换异常
4.2、迭代器意义
java中提供了很多种集合,它们在存储元素时采用的存储方式不同。所以当我们要取出这些集合中的元素时,可以通过一种通用的获取方式来完成
List是有序集合所以普通for可以遍历打印,而Set集合无序就不能用普通for去遍历打印,所以List可以有两种遍历方式而Set就只有一种就是迭代器
迭代器是一个接口,每一个具体的集合类都实现了 Iterator 接口,能够返回一个针对自己的存储结构的具体的 Iterator 接口的实现子类。Iterator向上层的使用者屏蔽了具体的底层集合类的实现细节,使用方法都一样
4.3、迭代过程中就想add集合元素怎么办(了解)
package normal.com.test;
import java.util.ArrayList;
import java.util.ListIterator;
//测试迭代过程中去add元素,借助ListIterator迭代器
public class CollectionTest05 {
public static void main(String[] args) {
//测试一:不用迭代器去遍历,用for循环遍历(过程中使用集合的add() )
//创建一个ArrayList对象
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
System.out.println(list.size());
//在cc后面添加kk
for (int i = 0; i < list.size(); i++) {
if ("cc".equals(list.get(i))) {
list.add(i + 1, "kk");
}
System.out.println(list.get(i));
}
public void add(int index,E element)
在此列表中的指定位置插入指定的元素。将当前位于该位置的元素(如果有)和
任何后续元素(向其索引添加一个)移动 //摘自API中文文档
所以结果是
5
aa
bb
cc
kk
dd
ee
/*
测试二:使用ListIterator迭代器来遍历并添加集合元素
需求是你想要迭代过程中去add集合元素,已知迭代过程中不能使用集合的add()加没有
迭代器的add(),那就用这个ListIterator迭代器去迭代及使用ListIterator迭代器的add()
*/
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
Object oit=it.next();
System.out.println(oit);
if ("cc".equals(oit)) {
it.add("kk");
}
}
//测试三:ListIterator逆向遍历
ListIterator<String> it2 = list.listIterator();
while (it2.hasPrevious()){
System.out.println(it2.previous());
}
System.out.println(it2.hasPrevious());
//是否还有上一个元素false,因为逆向遍历完成后指针指向了第一个元素
System.out.println(it2.hasNext()); //true
/*
了解即可,因为ListIterator迭代器在迭代过程中实现add集合元素就是拓展内容
这个ListIterator逆向遍历更加了解即可
猜测逆向遍历逻辑是指针指向集合.size()这个下标上,然后判断前面有无元素
有就取出前面一位下标上的元素
奇怪的是while循环内没有打印集合元素,为什么?
再当集合迭代完之后,指针指向了下标-1上,此时再调用Previous()自然是返回false
再看到调用hasNext(),这个不必说了
*/
}
}
4.4、为什么iterator()不写在Iterator接口中
Iterator接口也是Java集合中的一员,但它与Collection、Map接口有所不同。Iterator接口是JDK专门提供的,用于遍历集合中的所有元素
Java设计者让Collection继承于Iterable接口而不是Iterator接口。因为Iterable的子类Collection、Collection的子类List、Set等,这些是数据结构或者说放数据的地方。Iterator是定义了迭代逻辑的对象,让迭代逻辑和数据结构分离开来,这样的好处是可以在一种数据结构上实现多种迭代逻辑->不懂
每一次调用Iterable的Iterator()都会返回一个从头开始的Iterator对象,各个Iterator对象之间不会相互干扰,这样保证了可以同时对一个数据结构进行多个遍历。这是因为每个循环都是用了独立的迭代器Iterator对象
Iterator 为什么不是定义成一个类,而是选择的接口?
因为功能上没什么问题,但是由于一旦把针对不同具体集合的生成迭代器的方法放在一个类中。通常各个不同的迭代器的方法可能会有一些公共行为被抽成一个方法,就会因为一些公共操作的存在产生错综复杂的联系形成 上帝类(God Class) 啥都管,很难维护
Map 接口的迭代功能是嫁接Collection 接口的迭代功能,看Map 接口的抽象方法就知道了
具体怎么用在Map 集合的常用方法中讲到
// 通过键来遍历,返回值是Set集合,Map的key是Set集合,特性也和Set一样的
Set<K> keySet();
// 通过值来遍历,返回值是Collection
Collection<V> values();
// 通过键值对来遍历,返回值是Set集合
Set<Map.Entry<K, V>> entrySet();
4.5、Java的集合目前主要分为两种:Collection和Map
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,Java就提供了集合类->一说你肯定知道,但是问你数据用什么存?一下子你肯定想不到数组、集合这些
Collection和Map
Collection单值集合、Map双值集合;或者说前者是单列集合后者是双列集合(单列指输入一个数据,双列需要输入两个数据)
Collection表示一组对象,这些对象也称为Collection 的元素
Collection接口是Collection层次结构中的根接口。一些 collection 允许有重复的元素,而另一些则不允许。一些collection 是有序的,而另一些则是无序的。
JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如 Set 和 List)实现。Set集合没有索引,所以不能用for循环来遍历元素,只能通过增强for循环和迭代器来遍历元素
在哈希表的学习中便知道HashSet的数据结构是红黑树,其是有序的并且不能有重复的元素,Set也是不能有重复的元素
而HashMap的Key键也是红黑树.所以也不能有重复元素,但其值不是红黑树,所以可以重复
HashSet:哈希表实现
TreeSet: 红黑树实现
上图留下来是因为Set接口说了无序唯一的特点但是实现类HashSet的子类LinkedHashSet是有序的,这点值得记录一下
ArrayList的增删改查方法
public boolean add(E e) { //增
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public E remove(int index) { //删
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public E set(int index, E element) { //改
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
public E get(int index) { //查
rangeCheck(index);
return elementData(index);
}
对比ArrayList的内部类Itr只提供一个remove(),所以
Java中三种长度表现形式
数组.length 属性 返回值 int
字符串.length() 方法,返回值 int
集合.size() 方法, 返回值 int
面试题:Hashtable和HashMap的区别?
Hashtable是线性安全的,HashMap是线性不安全的,所以HashMap效率更高
StringBuffer是线性安全的,StringBuilder是线性不安全的。所以单线程使用StringBuilder,效率高
Hashtable不允许使用null作为key和value,否则会引发异常,而HashMap可以
Map集合:Key值不可以重复(key等于Set集合),Value值可以重复
数据结构:数组、双向链表、哈希表、二叉树;关于数据底层存储结构,决定了集合绝大部分的特性,如:查询和增删快慢,是否有序,是否可以重复。
线程安全:就是集合中的方法使用了同步锁关键字:synchronized;有锁的效率低
4.6、集合工具(Collections)
java.utils.Collections
集合工具类,就是用来简化对集合进行操作的
常用的方法
pubic stativc <T> boolean addAll(Collection<T> c,T... elements)
往集合中批量添加元素
public static void shuffle(List<?> list)
乱序
public static <T> void sort(List<T> list)
将集合中的元素按昭默认规则排序
public static <T> void sort(List<T> list, Comparator<? super T>)
将集合中的元素按照指定规则进行排序
使用
pubic stativc <T> boolean addAll(Collection<T> c,T... elements)
往集合中批量添加元素
List<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d");
System.out.println(list); //[a, b, c, d]
public static void shuffle(List<?> list)
乱序
List<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d");
System.out.println(list); //[a, b, c, d]
Collections.shuffle(list);
System.out.println(list); //[d, b, a, c]随机的
public static <T> void sort(List<T> list)
将集合中的元素按昭默认规则排序
List<String> list = new ArrayList<>();
//无序的
Collections.addAll(list, "b", "d", "g", "o");
Collections.sort(list);
System.out.println(list); //[b, d, g, o]
public static <T> void sort(List<T> list, Comparator<? super T>)
将集合中的元素按照指定规则进行排序
List<Integer> list = new ArrayList<>();
list.add(9);
list.add(3);
list.add(4);
list.add(2);
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
//return o2 - o1; //[9, 4, 3, 2] 降序
return o1 - o2; //[2, 3, 4, 9] 升序
}
});
System.out.println(list);
4.7、集合和数组的区别
集合和数组既然都是容器,它们有啥区别呢?
数组的长度是固定的。集合的长度是可变的(通过动态扩容来达到长度可变,集合可以自己设置合理的长度,来减少扩容带来的资源浪费)
数组中存储的是同一类型的元素,可以存储任意类型数据。集合存储的都是对象,对象的类型可以不一致。如果想存储基本类型数据需要存储对应的包装类型。如果想存储某一种类型的元素,需要通过泛型来指定元素类型,否则默认是Object类。在开发中一般当对象多的时候,使用集合进行存储(数组的长度不可变,不够灵活,需要我们自己去扩容,比较麻烦。而集合就不需要考虑这个了,并且里面封装了查找,新增,修改,删除元素等方法,用起来很方便,就不需要我们自己手动去写这些方法了)