第九章 Collection集合
9.1 集合概述
在前面基础班我们已经学习过并使用过集合ArrayList<E> ,那么集合到底是什么呢?
-
集合:集合是java中提供的一种容器,可以用来存储多个数据。
集合和数组既然都是容器,它们有什么区别呢?
-
数组的长度是固定的。集合的长度是可变的。
-
数组中存储的是同一类型的元素,可以存储任意类型数据。集合存储的都是引用数据类型。如果想存储基本类型数据需要存储对应的包装类型。(注意回顾包装类型哦)
小结:
9.2 集合常用类的继承体系
Collection特点:
Collection集合体系:
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.List
和java.util.Set
。其中,List
的特点是元素有序、有索引、元素可重复。Set
的特点是无序、无索引、元素不可重复。List
接口的主要实现类有java.util.ArrayList
和java.util.LinkedList
,Set
接口的主要实现类有java.util.HashSet
和java.util.LinkedHashSet
。
从上面的描述可以看出JDK中提供了丰富的集合类库,为了便于初学者进行系统地学习,接下来通过一张图来描述集合常用类的继承体系
注意:这张图只是我们常用的集合有这些,不是说就只有这些集合。
集合本身是一个工具,它存放在java.util包中。在Collection
接口定义着单列集合框架中最最共性的内容。
9.3 Collection 常用API
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
public boolean add(E e)
: 把给定的对象添加到当前集合中 。
public void clear()
:清空集合中所有的元素。
public boolean remove(E e)
: 把给定的对象在当前集合中删除。
public boolean contains(Object obj)
: 判断当前集合中是否包含给定的对象。
public boolean isEmpty()
: 判断当前集合是否为空。
public int size()
: 返回集合中元素的个数。
public Object[] toArray()
: 把集合中的元素,存储到数组中
tips: 有关Collection中的方法可不止上面这些,其他方法可以自行查看API学习。
代码示例:
public class Demo1 {
public static void main(String[] args) {
//强调:以后该怎么创建对象,还是怎么创建对象
//现在我们是为了要学习Collection接口中的方法
//所以才用多态方式创建对象的。
Collection<String> list = new ArrayList<>();
//add方法表示默认将元素添加到末位
//返回值表示当前元素是否添加成功
//ArrayList一定是添加成功的,所以返回true!这个基础班的时候提到过哦!!
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
System.out.println(list);
//清空方法
//list.clear();
//System.out.println(list);
//删除方法 remove(对象)
//注意:在Collection只有一个删对象的方法,没有通过索引删除的方法
//因为Collection还有第二个分支:Set。而Set集合是没有索引的。
//boolean result1 = list.remove("aaa");
//System.out.println(result1);
//boolean result2 = list.remove("eee");
//System.out.println(result2);
//System.out.println(list);
//判断是否包含的方法
//contains 判断是否包含
boolean result3 = list.contains("aaa");
System.out.println(result3);
boolean result4 = list.contains("eee");
System.out.println(result4);
//判断集合是否为空
System.out.println(list.isEmpty());
//获取集合的长度
System.out.println(list.size());
}
}
每一步的运行结果大家自己手动敲运行一下!
第二章 Collection的三种通用遍历方式(掌握)
迭代器遍历
增强for遍历
lambda表达式遍历
因为Collection没有索引,所以不能用普通for
注意:
基础班学习的普通for只适用于List集合,而Set没有索引,是不能用普通for遍历的。
2.1 Iterator接口
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator
。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:
-
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的。
下面介绍一下迭代的概念:
-
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
Iterator接口的常用方法如下:
public E next()
:返回迭代的下一个元素。
public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。这两个方法是结合使用的,在今后的开发中,这俩方法几乎是形影不离。
接下来我们通过案例学习如何使用Iterator迭代集合中元素:
//1.创建集合
Collection<String> list = new ArrayList<>();
//2.添加元素
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
//3.遍历
//3.1获取迭代器对象(箭头,指针),默认指向集合的0索引位置
Iterator<String> it = list.iterator();
//3.2循环判断当前指向的位置是否有元素
while(it.hasNext()){
//3.3获取元素并移动指针
String str = it.next();
System.out.println(str);
}
tips:
在进行集合元素获取时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会抛出java.util.NoSuchElementException没有集合元素异常。
在进行集合元素获取时,如果添加或移除集合中的元素 , 将无法继续迭代 , 将会抛出ConcurrentModificationException并发修改异常.
2.2 迭代器的实现原理
我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程:
在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。
3.3增强for
增强for相当于是迭代器的简化写法。它既可以用于集合也可以用于数组。
注意点:
只有实现了Iterable接口的类,才能用增强for和迭代器遍历。
核心:
简化迭代器书写,底层其实也是一个迭代器。
作用:
可以用来遍历集合或者数组
格式:
for(数据类型 变量名: 集合/数组){ //遍历操作 }
遍历数组:
之前基础班学过的快捷方式 数组名.fori 然后回车。这是之前遍历数组的快捷方式,现在只需要稍稍改动一下即可 数组名.for 然后回车。注意上诉代码那个int i是一个局部变量,它是用来接收数组元素的。改变它的值不改变数组元素的值!!
索引自动加1不是那个i在自动加1,那个i是用来接收数组元素的,而是整个增强for循环在自动加1。
遍历集合:
代码示例:
//1.创建集合 ArrayList<String> list = new ArrayList<>(); //2.添加元素 list.add("aaa"); list.add("bbb"); list.add("ccc"); //3.遍历 for(String str : list){ System.out.println(str); }
运行结果:
3.4增强for的注意点:
增强for中的变量,仅仅是一个第三方变量而已,我们是遍历集合得到每一个元素,把每一个元素赋值给第三方变量,如果我们在循环的过程中,修改了第三方变量的值,是不会改变集合里面原本的值。
代码示例:
//1.创建集合 ArrayList<String> list = new ArrayList<>(); //2.添加元素 list.add("aaa"); list.add("bbb"); list.add("ccc"); //3.遍历 for(String str : list){ //此时没有索引的概念 //str依次表示集合里面的每一个元素 //System.out.println(str); //此时的str仅仅是一个第三方变量而已 //我们是遍历集合得到每一个元素,把每一个元素赋值给第三方变量 //如果我们在循环的过程中,修改了第三方变量的值,是不会改变集合里面原本的值。 str = "QQQ"; } for (int i = 0; i < list.size(); i++) { //利用普通for遍历,也是一样的,获取元素赋值给第三方变量 String str = list.get(i); //修改第三方变量的值,不会影响集合中的元素 str = "QQQ"; } //4.循环结束之后打印集合 System.out.println(list);
3.5lambda表达式遍历集合
遍历用到的方法:
方法名 | 作用 |
---|---|
void forEach(Consumer<? super T> action): | 结合lambda遍历集合 |
代码示例:
package com.itheima.a03arraylistdemo; import java.util.ArrayList; import java.util.function.Consumer; public class Demo4 { public static void main(String[] args) { //1.创建集合 ArrayList<String> list = new ArrayList<>(); //2.添加元素 list.add("aaa"); list.add("bbb"); list.add("ccc"); //3.遍历集合 //forEach方法在底层其实也是进行了循环遍历 //在遍历的过程中,得到了每一个元素,再调用下面的accept方法 System.out.println("----------匿名内部类-----------------"); list.forEach( new Consumer<String>() { @Override public void accept(String s) { //参数s,依次表示集合中的每一个数据 System.out.println(s); } } ); System.out.println("----------lambda表达式-----------------"); list.forEach( (String s) -> { //参数s,依次表示集合中的每一个数据 System.out.println(s); } ); System.out.println("----------lambda表达式简写格式-----------------"); list.forEach(s -> System.out.println(s));//掌握 } }
3.6 练习-添加自定义对象并遍历
需求:
某影院系统需要在后台存储上述三部电影,然后依次展示出来。
属性:电影名name 评分score 主演acotr
分析
①:定义一个电影类,定义一个集合存储电影对象。
②:创建3个电影对象,封装相关数据,把3个对象存入到集合中去。
③:遍历集合中的3个对象,输出相关信息。
代码示例:
public class Demo5 { public static void main(String[] args) { //创建集合对象 Collection<Moive> list = new ArrayList<>(); //添加元素 Moive m1 = new Moive("肖生克的救赎", 9.7, "罗宾斯"); Moive m2 = new Moive("霸王别姬", 9.6, "张国荣、张丰毅"); Moive m3 = new Moive("阿甘正传", 9.5, "汤姆.汉克斯"); list.add(m1); list.add(m2); list.add(m3); //遍历 //迭代器 System.out.println("----------------迭代器-----------------"); Iterator<Moive> it = list.iterator(); while (it.hasNext()) { Moive moive = it.next(); System.out.println(moive); } System.out.println("----------------增强for-----------------"); for (Moive moive : list) { System.out.println(moive); } System.out.println("----------------匿名内部类-----------------"); list.forEach(new Consumer<Moive>() { @Override public void accept(Moive moive) { System.out.println(moive); } }); System.out.println("----------------lambda表达式-----------------"); list.forEach((Moive moive) -> { System.out.println(moive); }); System.out.println("----------------lambda表达式简写格式-----------------"); list.forEach(moive -> System.out.println(moive)); } }
这个代码能自己手动写出来而且要熟练就差不多掌握了。在此强调,Lamada表达式和匿名内部类联席紧密,学不好匿名内部类就理解不了Lambda表达式。Lambda表达式和Lambda式的简写是非常重要且容易忘记的内容,大家学习的时候这部分多花一点时间。因此在学习这几节内容时,最好一起学习。一起练习,综合练习才能掌握更牢固。
集合内存分布:
集合存的不是对象本身,而是该对象的地址,通过该地址找到该对象。
扩展:如果集合中存储的是自定义对象,那么在利用contains判断元素是否存在和利用remove删除元素时,要重写equals方法。为什么?
首先我们要知道一点的是,当我们判断一个对象属不属于该集合不是看这个对象的地址是不是和该集合的某个元素地址一致,而是看该对象的所有属性和几集合的某个元素的所有属性是否一致。打个比方:就好比入你在某高中高三5班读书,但是现在你在家,我们判断你属不属于5班不是看你现在所在的地址是不是和高三五班这个班级的地址是否一致。而是看你的属性值和当前班级的某个成员是否一致。如果一致那你就属于这个班级。
这是结论,下面我来讲解为什么?如下:
学生对象s1和s2是不同的对象,但是他们的属性是一样的,也就是说给我们的感觉是他俩是同一个人,只是所在地址不同。但是学生s2没有添加到集合中,判断这个元素是否在集合中用contains方法。答案是false,说明不再集合中,但是s1的属性和s2的属性一模一样,理应来说应该在集合中才对。那么是怎么判断的呢?可以把当前contains转到定义看一下。
这里的equals是Object类的equals,它是用 ==比较的。比较的是地址值。但是我们对一个对象在不在集合中不是用地址进行判断,而是判断这个对象的属性是不是和集合中某个元素的属性相等。因此要在Student类中重写equals方法。
重写后再运行如下,这是重写的,前面学习Object已经学过怎么重写了,快捷键
alt+insert,选择equals即可。
重写后再次运行:
同理,remove方法同理。
这是在没有重写equals方法时删除对象的结果,和上面一样,在没有重写的情况下是调用Object类里的equals方法,进行的是地址的比较。我们删除元素肯定是删除指定属性对象的元素。要达到这样的效果,就得重写equals方法。重写它以后,就进行的是属性之间的比较。
重写后再运行:
疑问:为什么重写equals方法就可以呢?我们转到集合类的remove方法的定义看一下:
再点击equals转到定义发现就是跳转到Object类的equals方法,即他们比较的是地址值,但是我们判断一个对象属不属于该集合中不是看这个对象的地址,二是看该对象的所有属性值是不是和集合中某个元素的所有属性值一致。因此要重写equals方法。
总结:使用集合时一定要重写equals方法!!