Java集合篇---附代码示例和理解(上)
Java集合框架中的Collection接口是单列集合的顶级接口,List和Set等集合都继承自它。集合具有长度可变、提供丰富操作方法的特点,只能存储引用数据类型。Collection接口提供了add()、remove()、contains()等常用方法。迭代器Iterator用于遍历集合,通过hasNext()和next()方法实现遍历,但要注意并发修改问题。ArrayList作为List接口实
·
1集合框架(单列集合)
基本概念:集合是一个存放对象引用的容器
特点:
- 长度可变:集合的大小可以根据需要进行动态调整。
- 包含大量方法:集合提供丰富的操作方法,便于进行各种数据处理。
- 数据类型限制:集合只能存储引用数据类型的数据。
分类:
- a. 单列集合:每个元素独立构成集合的一部分。
- 示例:
list.add("张三")
- 示例:
- b. 双列集合:每个元素由两部分组成,即
key
和value
,形成键值对。- 示例:
map.put("Yegar","19")
,其中"年龄"
为key
,"19"
为value
。
- 示例:
单列集合的架构:
采自尚硅谷,与数据结构相关
2Collection接口
1. 概述
- 单列集合的顶级接口:Collection接口是单列集合的顶级接口,所有单列集合(如List、Set)都继承自该接口。
2. 使用
- 创建:
- 语法:
Collection<E> 对象名 = new 实现类对象<E>()
- 泛型:
<E>
决定了集合中能存储什么类型的数据,可以统一元素类型。- 泛型中只能写引用数据类型,如果不写,默认为
Object
类型,此时可以存储任何类型的数据。 - 示例:
<int>
:错误(int
是基本数据类型,不是引用类型)。<Integer>
:正确(Integer
是引用类型)。<Person>
:正确(Person
是自定义的引用类型)。
- 泛型中只能写引用数据类型,如果不写,默认为
- 语法:
- 泛型细节:
- 等号前面的泛型必须写,等号后面的泛型可以不写,JVM会根据前面的泛型推导出后面的泛型类型。
3. 常用方法
- add(E e):将给定的元素添加到当前集合中(调用
add
时,一般不用boolean
接收,因为add
一定会成功)。 - addAll(Collection<? extends E> c):将另一个集合的元素添加到当前集合中(集合合并)。
- clear():清除集合中所有的元素。
- contains(Object o):判断当前集合中是否包含指定的元素。
- isEmpty():判断当前集合中是否有元素(即集合是否为空)。
- remove(Object o):将指定的元素从集合中删除。
- size():返回集合中的元素个数。
- toArray():把集合中的元素存储到数组中。
代码示例:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class Main
{
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
//boolean add(E e) : 将给定的元素添加到当前集合中 (我们一般调add时,不用boolean接收,因为add一定会成功)
collection.add("艾伦耶格尔");
collection.add("三里");
collection.add("兵长");
collection.add("猫猫");
collection.add("狗狗");
collection.add("利威尔");
System.out.println(collection);
//boolean addAll(Collection<? extends E> c) :将另一个集合元素添加到当前集合中 (集合合并)
Collection<String> collection1 = new ArrayList<>();
collection1.add("花洒");
collection1.add("吉娃娃");
collection1.add("猎犬");
collection1.add("小吴");
collection1.addAll(collection);
System.out.println(collection1);
//void clear() :清除集合中所有的元素
collection1.clear();
System.out.println(collection1);
//boolean contains(Object o) :判断当前集合中是否包含指定的元素
boolean result01 = collection.contains("Yeagar");
System.out.println("result01 = " + result01);
//boolean isEmpty() : 判断当前集合中是否有元素->判断集合是否为空
System.out.println(collection1.isEmpty());
//boolean remove(Object o) :将指定的元素从集合中删除
collection.remove("涛哥");
System.out.println(collection);
//int size() :返回集合中的元素个数。
System.out.println(collection.size());
//Object[] toArray() : 把集合中的元素,存储到数组中
Object[] arr = collection.toArray();
System.out.println(Arrays.toString(arr));
}
}
测试结果:
3迭代器
- 概述:Iterator接口用于遍历集合。
- 主要作用:遍历集合。
- 获取方式:通过
Collection
接口中的iterator()
方法获取Iterator<E>
对象。 - 核心方法:
boolean hasNext()
:判断集合中是否还有下一个元素。E next()
:获取集合中的下一个元素。
简要分析
- Iterator接口:是Java中用于遍历集合的标准接口,提供了一种统一的遍历集合元素的方式。
- 遍历机制:通过
hasNext()
方法检查是否还有下一个元素,若有则通过next()
方法获取该元素,以此循环实现集合的遍历。 - 使用场景:适用于需要顺序访问集合中元素且不需要索引的场景,是集合遍历的常用方式之一。
代码示例
import java.util.ArrayList;
import java.util.Iterator;
public class DemoOlIterator {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("艾伦·耶格尔");
list.add("三笠·阿克曼");
list.add("阿明·阿诺德");
list.add("利威尔");
list.add("韩吉·佐耶");
// 获取迭代器对象
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
}
}
测试结果
1. 注意事项
- next方法的使用:在使用
next
方法获取元素时,不要连续多次调用,否则会引发NoSuchElementException
(没有可操作的元素异常)。
代码:
public class DemoOlIterator {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("艾伦·耶格尔");
list.add("三笠·阿克曼");
list.add("阿明·阿诺德");
list.add("利威尔");
list.add("韩吉·佐耶");
// 获取迭代器对象
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
//String element2 = iterator.next();//判断一个调用两个会在第六个无法访问
//System.out.println(element2);
}
}
}
- 迭代逻辑:
- 创建一个
ArrayList
并添加多个字符串元素。 - 获取该列表的迭代器
Iterator
。 - 使用
while
循环和hasNext
方法检查是否还有下一个元素。 - 通过
next
方法获取并打印当前元素。 - 错误示例:在一次循环中连续调用
next
方法(如注释掉的代码),这会导致NoSuchElementException
,因为一次next
调用已经移动了迭代器的指针,再次调用时指针可能已经超出范围。
- 创建一个
迭代过程
next()
方法的逻辑核心是通过 cursor
和 lastRet
两个指针来管理迭代过程:
- 检查并发修改:首先调用
checkForComodification()
方法,确保在迭代过程中列表没有被修改(即expectedModCount
与modCount
一致)。 - 获取当前元素索引:将
cursor
的值赋给i
,i
表示当前要返回的元素的索引。 - 检查索引有效性:
- 如果
i
大于等于size
,抛出NoSuchElementException
,表示没有更多元素可迭代。 - 如果
i
大于等于elementData.length
,抛出ConcurrentModificationException
,表示列表结构在迭代过程中被修改。
- 如果
- 移动
cursor
指针:将cursor
增加 1,指向下一个元素。 - 返回当前元素:将
elementData[i]
转换为泛型E
并返回,同时将lastRet
设为i
,记录上次返回元素的索引。 - 双指针移动原理
代码
public E next() {
// 检查是否发生并发修改
checkForComodification();
// 获取当前元素的索引
int i = cursor;
// 检查索引是否越界
if (i >= size)
throw new NoSuchElementException();
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 移动 cursor 指针到下一个元素
cursor = i + 1;
// 返回当前元素,并更新 lastRet
return (E) elementData[lastRet = i];
}
4迭代器源码刨析
迭代器底层原理总结
1. 迭代器的获取方式
迭代器(Iterator
)通过集合的 iterator()
方法获取。例如:
Iterator<String> iterator = list.iterator();
2. Iterator
接口的实现
Iterator
是一个接口,其具体实现由集合类的内部类负责。ArrayList
的iterator()
方法 返回其内部类Itr
的实例,Itr
实现了Iterator
接口。
3. ArrayList
的 iterator()
方法
public Iterator<E> iterator() {
return new Itr();
}
Itr
是ArrayList
的一个 私有内部类,实现了Iterator<E>
接口。
4. 其他集合的迭代器实现
HashSet
的iterator()
方法:
public Iterator<E> iterator() {
return map.keySet().iterator();
}
-
HashSet
内部使用HashMap
来存储元素,因此其iterator()
方法实际返回的是HashMap
的keySet()
的迭代器。
HashMap
的keySet().iterator()
方法:public Iterator<K> iterator() { return new KeyIterator(); }
KeyIterator
是HashMap
的一个内部类,继承自HashIterator
并实现了Iterator<K>
接口。
5. 总结
Iterator
接口的实现依赖于具体集合类的内部类。ArrayList
使用Itr
作为其迭代器的实现类。HashSet
通过HashMap
的keySet()
来实现迭代器,具体由KeyIterator
(HashMap
的内部类)来实现。
关键点
- 迭代器的获取:通过集合的
iterator()
方法。 - 迭代器的实现:由集合类的内部类具体实现
Iterator
接口。 - 不同集合的迭代器实现不同:
ArrayList
使用Itr
,HashSet
依赖HashMap
的KeyIterator
。
并发修改异常
示例
public class Demo03Iterator {
public static void main(String[] args) {
//需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马
ArrayList<String> list = new ArrayList<>();
list.add("唐僧");
list.add("孙悟空");
list.add("猪八戒");
list.add("沙僧");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String element = iterator.next();
if ("猪八戒".equals(element)){
list.add("白龙马");
}
}
System.out.println(list);
}
}
测试结果
1. 问题核心
- 主要问题:在使用
Iterator
遍历集合时,如果在遍历过程中修改集合(如添加元素),会导致ConcurrentModificationException
(并发修改异常)。 - 原因分析:
Iterator
在遍历集合时,会检查modCount
(实际操作次数)和expectedModCount
(预期操作次数)是否相等。如果不相等,就会抛出ConcurrentModificationException,而add方法会进行modCount++;导致不相等。
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
2. 解决方案
- 使用
ListIterator
:ListIterator
是Iterator
的子接口,它不仅支持遍历集合,还支持在遍历过程中安全地修改集合(如添加、删除元素)。
public class Demo03Iterator {
public static void main(String[] args) {
// 需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马
ArrayList<String> list = new ArrayList<>();
list.add("唐僧");
list.add("孙悟空");
list.add("猪八戒");
list.add("沙僧");
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
String element = listIterator.next();
if ("猪八戒".equals(element)) {
listIterator.add("白龙马"); // 使用 listIterator.add 安全添加元素
}
}
System.out.println(list);
}
}
测试结果
5ArryayList
1. 概述
- ArrayList 是 List 接口的实现类。
2. 特点
- 元素有序:按照存储顺序存取。
- 元素可重复:允许存储重复元素。
- 有索引:支持通过索引操作元素。
- 线程不安全:在多线程环境下使用需额外同步。
3. 数据结构
- 数组:底层使用数组实现。
4. 常用方法
- add(E e):将元素添加到集合尾部(方法调用一定成功,返回值通常忽略)。
- add(int index, E element):在指定索引位置添加元素。
- remove(Object o):删除指定元素,成功返回
true
,失败返回false
。 - remove(int index):删除指定索引位置的元素,返回被删除的元素。
- set(int index, E element):将指定索引位置的元素修改为
element
。 - get(int index):根据索引获取元素。
- size():获取集合中元素的个数。
代码方法示例
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// boolean add(E e) -> 将元素添加到集合中->尾部
list.add("藏原走");
list.add("清濑灰二");
list.add("穆萨");
list.add("尼古");
list.add("王子");
System.out.println(list);
// void add(int index, E element) ->在指定索引位置上添加元素
list.add(2, "城太郎");
System.out.println(list);
// boolean remove(Object o) ->删除指定的元素,删除成功为true,失败为false
list.remove("城太郎");
System.out.println(list);
// E remove(int index) -> 删除指定索引位置上的元素,返回的是被删除的那个元素
String element = list.remove(0);
System.out.println(element);
System.out.println(list);
// E set(int index, E element) -> 将指定索引位置上的元素,修改成后面的element元素
String element2 = list.set(0, "神童");
System.out.println(element2);
System.out.println(list);
// E get(int index) -> 根据索引获取元素
System.out.println(list.get(0));
// int size() -> 获取集合元素个数
System.out.println(list.size());
}
}
测试结果
5遍历
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("藏原走");
list.add("清濑灰二");
list.add("穆萨");
list.add("尼古");
list.add("王子");
// 使用迭代器遍历
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("=====================");
// 使用for循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("=====================");
// 使用fori快捷键生成的循环遍历list.fori
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
测试结果
6ArryayList源码剖析总结
1. 构造方法机制
默认构造方法 ArrayList()
// 静态空数组常量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 实际存储数据的数组
Object[] elementData;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
-
延迟初始化:创建ArrayList对象时并不会立即分配容量为10的数组
-
首次添加触发:只有在第一次调用
add()
方法时才会创建初始容量为10的数组
指定容量构造方法 ArrayList(int initialCapacity)
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity]; // 直接创建指定容量数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA; // 使用空数组
} else {
throw new IllegalArgumentException("非法容量: " + initialCapacity);
}
}
-
立即分配:根据传入的初始容量立即创建相应大小的数组
2. 添加元素与扩容机制
add() 方法调用链
public boolean add(E e) {
modCount++; // 修改计数器,用于快速失败机制
add(e, elementData, size); // 调用内部添加方法
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) // 检查是否需要扩容
elementData = grow(); // 执行扩容
elementData[s] = e; // 放置元素
size = s + 1; // 更新大小
}
扩容核心逻辑
private Object[] grow() {
return grow(size + 1); // 至少需要size+1的容量
}
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 正常扩容:按1.5倍增长
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, // 最小增长量
oldCapacity >> 1 // 首选增长量:原容量的一半
);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
// 首次扩容:取默认容量10和所需容量的最大值
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
关键特性总结
-
延迟初始化:默认构造器不会立即创建数组,首次添加时才分配内存
-
扩容策略:
-
首次扩容:创建容量为10的数组
-
后续扩容:按原容量的1.5倍进行扩容
-
-
可变长度实现:通过
Arrays.copyOf()
创建新数组并复制数据来实现 -
线程不安全:没有同步机制,多线程环境下需要外部同步
扩容过程流程图
更多推荐
所有评论(0)