文章目录
前言
前面已经学习过一些集合的基本知识:包装类、泛型,以及一个基本的集合ArrayList。
本节我们将在前面的基础上面,更加系统的学习Java类里面集合这个概念。
首先,第一步我们要弄明白Java里面的集合是个什么东西,我们要先从集合的体系结构开始学习
并且,本节的一个重点就是关于各种实现类的源码分析,里面设计大量数据结构知识,对你的数据结构有极大帮助(极其极其重要!!!不要怕,学完结合给出的视频,你会发现你强的可怕。)
一、集合的体系结构
Java中的集合这个概念是一个体系,里面存在很多种不同的集合,我们可以将它们分成两大类来学习。
- 集合
- Collection(单列集合):里面元素是单个出现的
- Map(双列集合):里面元素的元素是成对出现的,存在映射关系
两大类下面又可以进一步划分
Collection(单列集合):里面元素是单个出现的
其中,Vector已经淘汰了不用管。
在上面结构图中,我们主要弄清楚哪些是接口、哪些是实现类
(1)List系列集合:添加的元素是有序、可重复、有索引的
(2)Set系列集合:添加的元素是无序、不重复、无索引的
Map(双列集合):里面元素的元素是成对出现的,存在映射关系
二、 Collection(单列集合)接口
1 Collection 系列通用方法
【注】:由于Collection是一个接口,我们必须要有一个实现类对象才可以演示,所以接下来的代码演示都是采用接口的多态+ArrayList的方式演示(特别是里面的接口多态要搞懂,我在写博客过程中一直强调这个东西,如果看过我之前的哪些博客,看到这里接口多态应该不是问题了)
好了,回归正题。
- collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
由于,其是一个顶级接口,那么下面的抽象方法,下面的实现类或多或少都会实现一些(由于适配器编程模式存在,肯定不是每个实现类都实现了所有),但其中有几个,所有的底层实现类都实现了的。我们要知道,这会减轻我们学习下面实现类的难度。
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数/集合的长度 |
显然,这几个方法都是常用的,我们也应该牢牢记住的。
【注】:上面有几个方法底层是通过equals方法判断是否是同一个东西来进行判断包含或者删除了,注意对象是否重写了equals方法是关键。
接下来的代码演示都是采用接口的多态+ArrayList的方式演示上述方法的使用:
// 接口的多态性,编译看左边,运行看右边,运行的还是对应实现类里面重写过的方法
Collection<String> coll = new ArrayList<>();
// 1. add 方法
// 细节1:add 方法的返回值是 boolean 类型
// 细节2:add 方法如果我们往list集合中添加数据,一定是返回true,因为list集合是可以重复的
// 细节3:add 方法如果我们往set集合中添加数据,如果数据重复了,那么就会返回false
coll.add("hello");
coll.add("world");
coll.add("java");
System.out.println(coll); // [hello, world, java]
/*
// 2. clear 方法
// 清空集合中的所有元素
coll.clear();
System.out.println(coll); // []
*/
// 3. remove 方法 : 删除指定元素(如果有多个相同元素,只删除一个:删除第一个遇到的元素,所以有不确定性)
// 由于Collection 接口中的 remove要有共性,所以参数是 Object 类型,只能根据具体元素来删除
// 虽然我们用的更多的是根据索引来删除,但是Collection 下面的 Set 是没有索引的,不能抽成共性
// 所以 remove 方法是根据具体元素来删除的
// 返回值是 boolean 类型,如果有这个元素,就删除,返回true,没有这个元素,就不删除,返回false
// 细节:底层调用的是 equals 方法,
// 所以我们自定义类的时候,如果只要判断属性值相同就认为是同一个对象,需要重写 equals 方法才能根据值来判断是同一个对象。不然就是根据地址来判断的
// 另外String的 equals 方法已经重写了,所以可以直接用,我们就不需要管了
coll.remove("world");
System.out.println(coll); // [hello, java]
// 4. contains 方法 : 判断集合中是否包含指定元素
// 返回值是 boolean 类型
// 细节:底层调用的是 equals 方法,
// 所以我们自定义类的时候,如果只要判断属性值相同就认为是同一个对象,需要重写 equals 方法才能根据值来判断是否包含。不然就是根据地址来判断的
// 另外String的 equals 方法已经重写了,所以可以直接用,我们就不需要管了
System.out.println(coll.contains("hello")); // true
System.out.println(coll.contains("world")); // false
// 5. isEmpty 方法 : 判断集合是否为空
// 返回值是 boolean 类型
System.out.println(coll.isEmpty()); // false
// 6. size 方法 : 获取集合中元素的个数
// 返回值是 int 类型
System.out.println(coll.size()); // 2
上面代码主要有两个方法,注意底层源码是怎么比较是同一个对象的,所以我们需要注意。
上面代码里面的注释如果看不懂了:参考视频
如果前面知识没有忘记的话应该是记得我注释是什么意思的(主要是Object类equals方法的重写与否!!)
2 Collection 系列的遍历
【注】:Collection 系列的List系列有索引,之前的普通遍历方式只要有索引(List系列)任然可以用,但Set系列没有索引,之前的普通遍历方式就不能使用了,只能使用下面三种Collection 系列的通用变量方式。
(1)迭代器遍历和ArrayList中的迭代器的源码分析
基本知识
- 迭代器:Java迭代器(Iterator)是 Java 集合框架中的一种机制,是一种用于遍历集合(如列表、集合和映射等)的接口;直接理解迭代器就是一种专门设计用来变量的类,没错是一个类它是,
解释一下迭代:在这里迭代就是变量的意思。
集合里面每个类的源码里面都设计了一个自己的Iterator泛型接口的实现类,并且提供了一个获取迭代器对象的方法。补充:在Java中,方法的返回类型可以是一个类或接口。当你定义一个返回父类类型的方法时,实际上意味着你可以返回该类型的任何子类对象。这是因为子类对象可以被自动向上转型为父类类型。
关于Java里面的迭代器对象有几点需要注意:
- (1)迭代器可以当成一个指针来理解:迭代器=指针
- (2)当你创建一个迭代器对象时,例如 Iterator iterator = list.iterator();,迭代器的是指向第一个元素(看ArrayList里面源码,初始是指向索引0处,Set系列源码我没有看,应该也差不多)
- (3)这个指针提供了一个获取当前指针处元素并指针向后移动一位的方法
【注】:返回的是指针没有移动时指向的元素,不是移动后指针指向的元素,这个一定要搞明白。这个看源码你就会发现很巧妙
【注意事项】:
(1)迭代器遍历时不能用集合的方法进行元素的删除和增加,可以使用迭代的remove()方法(这个方法进行了防护);边遍历边改变集合的长度这是大忌,千万不要注意
(2)迭代器遍历结束后指针是不会复位的,因此如果还有进行第二轮变量,需要创建新的迭代器对象
(3)while循环中最好next方法只使用一次,因为调用多次next,指针也移动了多次,很显然这样到空位置有报错的风险了就
如果用过Python里面链表的cur这种方式模拟指针遍历,这里应该能够理解。(有C++指针基础的理解这个就更加没有问题了)
Iterator里面主要提供了三个方法:
- boolean hasNext():用于判断当前(迭代器)指针指向位置是否有元素
- E next():返回当前指针指向的元素,并指针向后移动一位(粗糙的理解为先返回指向元素,在移动指针吧)
注意:这个方法不光返回指针的下一个元素,还同时指针向后移动了一位了啊!
(粗糙的理解为先返回指向元素,在移动指针吧)---- 实际中这是不可能的,因为如果你先return方法就结束了。所以底层是指针先移动到下一位,但是有一个变量lastRet记录上一个位置,在根据这个位置就返回指针没有移动前的指向的元素了(看ArrayList源码发现的) - void remove():从集合中移除next()方法返回的最后一个元素(可选操作)。
【注】:上述方法和迭代器指针的指向和网上大部分资料讲的都不一样,但我可以保证我这里记录的是正确的,可以读源码验证。
所以我强烈建议学迭代器看源码看源码
ArrayList中的迭代器的源码分析视频
这个视频里面讲的非常好,认真听完,迭代器才能完全理解在干什么,不然就只能学到一个死套路。
主要是前两个方法就可以完成遍历了。
E next()方法
// 创建一个ArrayList并添加一些元素
Collection<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 获取集合的迭代器
Iterator<String> it = list.iterator();
// 输出集合中第一个元素
// 注意:迭代器指针it也会移动到下一个指向位置
System.out.println(it.next()); // Apple
// 输出集合中第二个元素
System.out.println(it.next()); // Banana
// 输出集合中第三个元素
System.out.println(it.next()); // Cherry
迭代器while遍历框架
// 创建一个ArrayList并添加一些元素
Collection<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
// 获取集合的迭代器
Iterator<String> it = list.iterator();
// 使用while循环遍历集合
while (it.hasNext()) {
String element = it.next(); // 获取it指针的下一个元素并将指针向后移动1位
System.out.println(element); // a b c d e
}
void remove():从集合中移除next()方法返回的最后一个元素(可选操作)。
// 创建一个ArrayList并添加一些元素
Collection<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(88);
list.add(78);
list.add(9);
// 获取集合的迭代器
Iterator<Integer> it = list.iterator();
// 使用while循环遍历集合
while (it.hasNext()) {
int element = it.next(); // 获取it指针的下一个元素并将指针向后移动1位
if (element < 10) {
it.remove(); // 删除it指针的上一个元素
}
}
System.out.println(list); // [88, 78]
Python中的可迭代对象和迭代器有什么关系吗?
没错,Python里面也是有迭代器的。Python中也可迭代对象和迭代器这两个概念,基本和Java是一样的
- 可迭代对象(Iterable):可迭代对象是一个可以返回迭代器的对象。
这些对象实现了__iter__()方法。例如:list、tuple、str、dict、set,以及range对象。
可迭代对象本身并不保存迭代状态。它们的__iter__()方法会返回一个新的迭代器对象,可以用来遍历其中的元素。 - 迭代器(Iterator):迭代器是一个实现了__iter__()和__next__()方法的对象。next()方法会返回下一个元素,直到没有元素为止,此时会引发StopIteration异常。
迭代器一次性返回元素,当它遍历完所有元素后就不能再次开始。
一个典型的例子就是 ,range(5)在Python中就是哟个可迭代对象,我们可以将其使用iter() 函数将 range 对象转为迭代器进行Java一样的迭代器遍历
# 创建一个 range 对象(可迭代对象)
r = range(5)
# 使用 iter() 函数将 range 对象转为迭代器
iterator = iter(r)
# 使用迭代器遍历 range 对象
print(next(iterator)) # 输出: 0
print(next(iterator)) # 输出: 1
print(next(iterator)) # 输出: 2
print(next(iterator)) # 输出: 3
print(next(iterator)) # 输出: 4
由于Python的for循环变量封装的很强大,就不必使用这中比较底层的迭代器遍历了。但是这里也了解一下吧
(2)增强for遍历(单列集合和数组适用)
-
所有的单列集合和数组才能用增强for进行遍历
-
语法格式:
快捷键:集合名.for
底层其实就是迭代器遍历:设计的初衷就是为了简化迭代器遍历,今后我们也是最多使用这种遍历方式。
int[] arr = {
1, 3, 5, 7, 9, 2, 4, 6, 8, 10};
for (int i: arr) {
System.out.print(i + " "); // 1 3 5 7 9 2 4 6 8 10
}
System.out.println();
Collection<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(5);
list.add(7);
list