打怪升级之小白的大数据之旅(十八)<Java面向对象进阶之集合一概述>
打怪升级之小白的大数据之旅(十八)
Java面向对象进阶之集合一概述
上次回顾
上一章,我们学习了Java中的一些常用核心类如Math,System,Arrays,包装类等,理解起来其实都不是很难,主要是方法比较多,当我们需要的时候,随机查询文档就好,本章开始对集合进行分享,我会将集合拆分开来慢慢讲解,集合不论是Java工程师,还是大数据都是比较重要的一个知识点。好了,开始进入正题…
集合
集合是什么?
- 集合是java提供的一种容器,可以用来存储多个对象数据
- 集合是java中的重点,基本上面试java中集合的考点最多,对于大数据来说,集合也是相对来说的重点
- 首先看一下Java集合的框架图
![集合框架图]()
我先介绍一下整个框架,然后再对内部进行讲解,这样便于大家理解:- 首先,collection和map,Collection 表示一组对象,Map表示一组映射关系或键值对
- 它们哥俩实际上是一个根接口,它们根据实际的不同功能,就有了子接口,如collection中的List和Set,map中的HashMap和Hashtable等
- 当有了这些子接口,我们就需要针对不同的需求场景定义不同的接口实现类,拿List来说,实现类就诞生出了ArrayList,LinkedList和Vector
- 了解清楚集合的构成之后,下面我介绍一下集合的迭代器,前面我们说到,集合是Java提供的一种容器,用来存储多个对象数据,那么自然我们就需要有一种方法来从容器中获取对象数据,这个方法就是迭代器接口,因为我们使用List集合情况很多,因此,专门有一个子迭代器接口Listlterator。
- 对象排序接口,容器工具类这些都是跟迭代器一样,为容器服务的接口,后面我们会详细介绍这两个
为什么要有集合?
- 集合的存储的元素是引用数据类型的容器
- 当我们看到这个概念时,不免会有疑问,集合是存储引用数据类型的容器,数组既可以存储引用数据类型,也可以存储基本数据类型,那么,为什么要有集合?
- 下面,我们来看看数组与集合的对比
| 数组 | 集合 |
|---|---|
| 数组创建后,长度不可变 | 集合创建后,长度可变 |
| 数组中提供的属性和方法较少,常用增删改查操作不方便 | 集合提供了更丰富的API可以对内部元素进行操作 |
| 数组存储数据的特点单一,只能是有序的,可重复的 | 集合存储多元化,可以对无序数据进行存储 |
| 数组中可以存储基本数据类型,也可以存储引用数据类型 | 集合只能存储引用数据类型 |
虽然集合只能存储引用数据类型,不过别忘了,我们上一章讲了包装类,因此,基本数据类型也可以存储进去哦
Collection
概述
- 前面提到了Collection是集合两大跟接口中的一个,Collection表示一组对象,这些对象也称为collection的元素
- 一些collection允许有重复的元素,一些不可以,因此,针对这个问题,我们就创建了两个子接口List和Sat来分别对collection进行存储
- 在java中,JDK不提供根接口Collection的任何直接实现,因此,Collection接口通常用来传递collection,并在需要最大普遍性的地方操作这些collection
- Collection
<E>是父接口,因此,它提供了可用于操作所有所属集合的方法,<E>是泛型,我们下面会简单讲解一下它的用法,后面会单独对其详细展开来讲
常用方法
- 添加元素
add(): 添加元素对象到当前结合中addAll(Collection other):添加other集合中的所有元素对象到当前集合中- 示例代码:
@Test public void test2(){ Collection coll = new ArrayList(); coll.add(1); coll.add(2); System.out.println("coll集合元素的个数:" + coll.size()); Collection other = new ArrayList(); other.add(1); other.add(2); other.add(3); coll.addAll(other); // coll.add(other); System.out.println("coll集合元素的个数:" + coll.size()); } - add与addAll方法有一个需要注意的点:
![在这里插入图片描述]()
- add方法在添加元素是,如果是单独添加,那么它在集合中就是单独存储的,如图第一排,当add方法添加一个数组时,它会将数组当作一个元素进行存储,如图右下那个,而addAll方法是会将数据单独进行存放
- 因此,当我们在对数据进行添加时,如果是单独的数据,那么使用add方法,当我们需要对一组数据进行添加时,就使用addAll方法
- 删除元素
- boolean remove(Object obj) :从当前集合中删除第一个找到的与obj对象equals返回true的元素。
- boolean removeAll(Collection<?> coll):从当前集合中删除所有与coll集合中相同的元素
- 判断
- boolean isEmpty():判断当前集合是否为空集合。
- boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素
- boolean containsAll(Collection<?> c):判断c集合中的元素是否在当前集合中都存在。即c集合是否是当前集合的“子集”
- 获取元素个数
- int size():获取当前集合中实际存储的元素个数
- 这个方法我们后面对集合的遍历会用到
- 交集
- boolean retainAll(Collection<?> coll):当前集合仅保留与c集合中的元素相同的元素,即当前集合中仅保留两个集合的交集
- 交集的意思就是两个集合中共有的部分
- 转为数组
- Object[] toArray():返回包含当前集合中所有元素的数组
- 示例代码:老样子,我就不一 一举例了,综合练习一下
import java.util.ArrayList; import java.util.Collection; public class Demo1Collection { public static void main(String[] args) { // 创建集合对象 // 使用多态形式 Collection<String> coll = new ArrayList<String>(); // 使用方法 // 添加功能 boolean add(String s) coll.add("小李广"); coll.add("扫地僧"); coll.add("石破天"); System.out.println(coll); // boolean contains(E e) 判断o是否在集合中存在 System.out.println("判断 扫地僧 是否在集合中"+coll.contains("扫地僧")); //boolean remove(E e) 删除在集合中的o元素 System.out.println("删除石破天:"+coll.remove("石破天")); System.out.println("操作之后集合中元素:"+coll); // size() 集合中有几个元素 System.out.println("集合中有"+coll.size()+"个元素"); // Object[] toArray()转换成一个Object数组 Object[] objects = coll.toArray(); // 遍历数组 for (int i = 0; i < objects.length; i++) { System.out.println(objects[i]); } // void clear() 清空集合 coll.clear(); System.out.println("集合中内容为:"+coll); // boolean isEmpty() 判断是否为空 System.out.println(coll.isEmpty()); } }
iterator迭代器
概述
- 我们现在知道了集合怎么创建了,下面肯定就需要对这个集合进行遍历,因此,下面我来为大家介绍一下迭代器iterator
- 首先普及一下迭代这个词:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代
- 针对经常需要遍历集合中所有元素的需求,JDK提供了一个接口
java.util.Iterator,它也是Java集合中的一员,Collection与Map接口主要用于存储元素,Iterator重要用于迭代访问(遍历)Collection中的元素,因此Iterator对象被称为迭代器
获取迭代器的方法
- public Iterator iterator()获取集合对应的迭代器,用来遍历集合中的元素的
- 注意一下,迭代器的使用并不是new()一个迭代器,而是直接使用集合.iterator()的方法进行使用
iterator接口的常用方法及使用
public E next():返回迭代的下一个元素。public boolean hasNext():如果仍有元素可以迭代,则返回 true。public void remove():通过迭代器删除元素- 接下来我们通过案例学习如何使用Iterator迭代集合中元素
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class Demo { public static void main(String[] args) { // 使用多态方式 创建对象 Collection coll = new ArrayList(); // 添加元素到集合 coll.add("串串星人"); coll.add("吐槽星人"); coll.add("汪星人"); //遍历 //使用迭代器 遍历 每个集合对象都有自己的迭代器 Iterator it = coll.iterator(); while(it.hasNext()){ //判断是否有迭代元素 String s = (String)it.next();//获取迭代出的元素,因为不知道it.next()的类型,因此需要向下转型 System.out.println(s); } } }
注: 在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误
迭代器的实现原理
- 我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素
- Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程:
![迭代器原理]()
- 具体步骤:
- 在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,指向第一个元素
- 当第一次调用迭代器的next方法时,返回第一个元素,然后迭代器的索引会向后移动一位,指向第二个元素,
- 当再次调用next方法时,返回第二个元素,然后迭代器的索引会再向后移动一位,指向第三个元素,
- 依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。
增强for与iterable接口
- 首先介绍一下增强for循环,我们前面的案例都是使用List的实现类ArrayList进行演示的,ArrayList等下会详细介绍,在上面的案例中,我们会发现ArrayList实例化的对象和数组很像,那么我们可不可以使用我们熟悉的数组遍历for循环进行遍历呢?
- 当然可以.我先卖个关子,等会介绍ArrayList再将怎么使用普通for循环进行遍历
- 前面学习了iterator对集合进行遍历,下面这个增强for循环,其原理就是使用迭代器实现的,只是使用增强for更加便捷,
- 定义格式
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); } } } // 遍历集合 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接口与增强for实现原理
- 在上面案例中,我们会发现,数组也可以使用增强for循环进遍历,这是为什么呢?下面就来介绍一下Iterable接口
- 它的作用就是实现这个接口允许对象成为 “foreach” 语句的目标
- Java 5时Collection接口继承了Iterable接口,因此Collection系列的集合就可以直接使用foreach循环遍历
- Iterable接口的抽象方法:
- public Iterator iterator(): 获取对应的迭代器,用来遍历数组或集合中的元素的。
- 自定义某容器类型,实现java.lang.Iterable接口,发现就可以使用foreach进行迭代。
- 自定义一个可以使用for循环的实现类
import java.util.Iterator; public class TestMyArrayList { public static void main(String[] args) { MyArrayList<String> my = new MyArrayList<>(); for(String obj : my) { System.out.println(obj); } } } class MyArrayList<T> implements Iterable<T>{ @Override public Iterator<T> iterator() { return null; } } - 我们发现,自定义的迭代器可以使用增强for循环进行遍历了,所以我们就可以得出前面总结的结论:增强for循环(for each)本质上就是使用Iterator迭代器进行遍历的
- 为了进一步理解这个机制,我们使用案例与断点调试来了解增强for循环的具体实现步骤:
import java.util.ArrayList;
import java.util.Collection;
public class TestForeach {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("陈琦");
coll.add("李晨");
coll.add("邓超");
coll.add("黄晓明");
//调用ArrayList里面的Iterator iterator()
for (String str : coll) {
System.out.println(str);
}
}
}
源码实现步骤:



modCount与fail-fast机制(快速失败机制)
- 当使用foreach或Iterator迭代器遍历集合时,同时调用迭代器自身以外的方法修改了集合的结构,例如调用集合的add和remove方法时,就会报ConcurrentModificationException
- 示例代码:
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; 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()); } } } - 什么是快速失败机制?
- 如果在Iterator、ListIterator迭代器创建后的任意时间从结构上修改了集合(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。
- 因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。
- 这样设计是因为,迭代器代表集合中某个元素的位置,内部会存储某些能够代表该位置的信息。当集合发生改变时,该信息的含义可能会发生变化,这时操作迭代器就可能会造成不可预料的事情
- 因此,果断抛异常阻止,是最好的方法。这就是Iterator迭代器的快速失败(fail-fast)机制
- 如何实现快速失败机制呢?
- 在ArrayList等集合类中都有一个modCount变量。它用来记录集合的结构被修改的次数。
- 当我们给集合添加和删除操作时,会导致modCount++。
- 然后当我们用Iterator迭代器遍历集合时,创建集合迭代器的对象时,用一个变量记录当前集合的modCount。例如:int expectedModCount = modCount;,并且在迭代器每次next()迭代元素时,都要检查 expectedModCount != modCount,
- 如果不相等了,那么说明你调用了Iterator迭代器以外的Collection的add,remove等方法,修改了集合的结构,使得modCount++,值变了,就会抛出ConcurrentModificationException。
- 正确的遍历删除
- 我们知道了快速失败机制之后,当确实有需求,需要进行元素删除,应该怎么办呢?其实吧,迭代器自己有一个remove()方法,调用这个方法就可以删除了
- 还是前面示例代码,我们再次对其元素进行删除:
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()){ // 这个删除是collection的方法,删除会报异常 // list.remove(iterator.next()); // 正确的删除方式 iterator.next();// 首先取出这个元素 iterator.remove(); // 然后调用迭代器的删除方法进行删除 } } }
总结
本章节着重从集合的概念,集合的基本方法、迭代器以及快速访问机制进行了分享,本章的一些案例都是使用ArrayList实现类进行举例说明的,因为我们下一章会对List接口以及其实现类ArrayList/LinkedList进行讲解。好了,本期内容就是这些,感谢大家耐心观看,欢迎后台吐槽。



浙公网安备 33010602011771号