集合概述
介绍
Java集合是使程序能够存储和操纵元素不固定的一组数据。 所有Java集合类都位于java.uti包中。
【问】:之前我们需要把多个元素放到一起的时候,使用的是数组。那么为何还要提供Java集合工具类呢?
我们通过对比数组和Java集合工具类来解释Java集合工具类的必要性。数组集合长度固定长度不固定存放任意类型不能存放基本数据类型,只能存放对象的引用
注意: 如果集合中存放基本类型,一定要将其 “装箱”成对应的”基本类型包装类”。
层次结构
Java的集合类主要由两个接口派生而出:Collection和Map。Collection和Map是Java结合框架的根接口,这两个接口又包含了一些子接口或实现类。
Collection的继承层次结构
Map的继承层次结构
总结
由以上两图我们可以看出Java集合类有清晰的继承关系,有很多子接口和实现类。但是,并不是所有子接口或实现类都是最常用的。
下面我们列举出最常用的几个子接口和实现类:
Collection ——> List ——> ArrayList类
Collection ——> List ——> LinkedList类
Collection ——> Set ——> HashSet类
Collection ——> Set ——> SortedSet接口 ——> TreeSet类
Map ——> HashMap类
Map ——> SortedMap ——> TreeMap类
Collection接口和Iterator
Collection介绍
Collection接口是List接口和Set接口的父接口,它定义的方法可以用于操作List集合和Set集合。
Collection接口定义的方法
方法 | 描述 |
boolean add(Object o) | 该方法用于向集合里添加一个元素,添加成功返回true |
void clear() | 清除集合里的所有元素,将集合长度变为0 |
boolean contains(Object o) | 返回集合里是否包含指定元素 |
boolean containsAll(Collection c) | 返回集合里是否包含集合c里的所有元素 |
int hashCode() | 返回此collection的哈希码值 |
boolean isEmpty() | 返回集合是否为空,当集合长度为0时,返回true |
Iterator iterator() | 返回一个Iterator对象,用于遍历集合里的元素 |
boolean remove(Object o) | 删除集合中指定元素o,当集合中包含了一个或多个元素o时,这些元素将被删除,该方法将返回true |
boolean removeAll(Collection c) | 从集合中删除集合c里包含的所有元素,如果删除了一个或一个以上的元素,返回true |
boolean retainAll(Collection c) | 从集合中删除不在集合c里包含的元素,如果删除了一个或一个以上的元素,返回true |
int size() | 返回集合中的元素个数 |
Object[] toArray() | 该方法把集合转换成一个数组,所有集合元素变成对应的数组元素 |
【示例】
使用ArrayList实现类演示以上方法
Iterator
【介绍】
1. Collection接口的iterator()和toArray()方法都用于获得集合中的所有元素,前者返回一个Iterator对象,后者返回一个包含集合中所有元素的数组。
2. Iterator接口隐藏底层集合中的数据结构,提供遍历各种类型集合的统一接口。
【接口主要方法】方法描述boolean hasNext()如果被迭代的集合有下一个元素,则返回trueObject next()返回集合里下一个元素void remove()删除集合里上一次next方法返回的元素
【示例】
使用西游记人物作为元素,演示以上方法的使用
【for与iterator对比】
Iterator的好处在于可以使用相同方式去遍历集合中元素,而不用考虑集合类的内部实现。
- 使用Iterator来遍历集合中元素,如果不再使用List转而使用Set来组织数据,则遍历元素的代码不用做任何修改
- 使用for来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样
- for循环需要下标
【示例】
使用Collection接口演示iterator访问不需要下标。(Collection接口没有提供下标访问)【foreach增强循环】
foreach遍历集合相当于获取迭代器,通过判断是否有下一个元素,执行操作。遍历数组相当于经典的for循环。优点缺点遍历的时候更加简洁
不用关心集合下标的问题。减少了出错的概率不能同时遍历多个集合
在遍历的时候无法修改和删除集合数据
List
【特点】
- List是一个有序集合,既存入集合的顺序和取出的顺序一致
- List集合允许添加的元素重复
List不单单继承了Collection的方法,还增加了一些新的方法。方法描述void add(int index, Object element)将元素element插入到List的index处boolean addAll(int index, Collection c)将集合c所包含的所有元素都插入在List集合的index处Object get(int index)返回集合index处的元素int indexOf(Object o)返回对象o在List集合中出现的位置索引int lastIndexOf(Object o)返回对象o在List集合中最后一次出现的位置索引Object remove(int index)删除并返回index索引处的元素Object set(int index, Object element)将index索引处的元素替换成element对象,返回新元素List subList(int fromIndex, int toIndex)返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合
【示例】
演示常用方法
ArrayList、Vector
【特点对比】
1. ArrayList和Vector都是基于数组实现的,两者用法差不多
2. ArrayList随机查询效率高,随机增删元素效率较低
3. Vector提供了一个Stack子类,模拟“栈”数据结构——”先进后出”
4. ArrayList是线程不安全的,Vector是线程安全的【重点示例】
ArrayList中存放对象,在判断包含或者删除元素时,通过equals比较是否为同一个对象,然后进行操作【示例】
将西游记取经团队5个成员放到Stack栈中,使用pop()和peek()操作元素,对比结果。
LinkedList
【特点】
- LinkedList是双向链表实现的
- 随机查询效率低,随机增删效率高
【LinkedList新增方法】方法描述void addFirst(Object e)将指定元素插入该集合的开头void addLast(Object e)将指定元素插入该集合结尾boolean offerFirst(Object e)将指定元素插入该集合的开头boolean offerLast(Object e)将指定元素插入该集合结尾boolean offer(Object e)将指定元素插入该集合结尾Object getFirst()获取,但不删除集合第第一个元素Object getLast()获取,但不删除集合最后一个元素Object peekFirst()获取,但不删除该集合第一个元素,如果集合为空,则返回nullObject peekLast()获取,但不删除该集合最后一个元素,如果集合为空,则返回nullObject pollFirst()获取,并删除该集合第一个元素,如果集合为空,则返回nullObject pollLast()获取,并删除该集合最后一个元素,如果集合为空,则返回nullObject removeFirst()获取,并删除该集合的第一个元素Object removeLast()获取,并删除该集合的最后一个元素Object pop()pop出该集合的第一个元素void push(Object e)将一个元素push到集合
【示例】
示例演示以上方法【总结】
- List主要有两个实现ArrayList和LinkedList,他们都是有顺序的,也就是放进去是什么顺序,取出来还是什么顺序
- ArrayList——遍历、查询数据比较快,添加和删除数据比较慢(基于可变数组)
- LinkedList——查询数据比较慢,添加和删除数据比较快(基于链表数据结构)
- Vector——Vector已经不建议使用,Vector中的方法都是同步的,效率慢,已经被ArrayList取代
- Stack——继承Vector实现的栈,栈结构是先进后出,但已被LinkedList取代
Set
【特点】
1. Set是一个无序集合,既存入集合的顺序和取出的顺序不一致
2. Set集合中元素不重复【常用方法】
方法描述boolean add(E e)如果此set中尚未包含指定元素,则添加指定元素boolean isEmpty()如果此set不包含任何元素,则返回trueboolean contains(Object o)如果此set包含指定元素,则返回 trueboolean remove(Object o)如果指定元素存在于此set中,则将其移除int size()返回此set中的元素的数量void clear()从此set中移除所有元素
【示例】
演示以上方法的使用
HashSet
HashSet是我们学习的重点,哈希原理我们在下面讲HashMap时,细讲。
TreeSet
TreeSet底层由TreeMap实现。可排序,默认自然升序。
【示例】
向TreeSet对象中添加几个元素。遍历打印出它们,显示有序。
【总结】
- HashSet底层由HashMap实现
- TreeSet底层由TreeMap实现
- <详解见HashMap、TreeMap>
Collections工具类和Comparable、Comparator比较器
Collections工具类
Collections是一个包装工具类。它包含有各种有关集合操作的静态多态方法,此类不能实例化,服务于Java的Collection接口。
【常用方法】
sort、reverse、fill、copy、max、min、swap等【重点讲解:Sort排序】
其他方法,我们自行练习
public static <T extends Comparable<? super T>> void sort(List<T> list) 1. 根据元素的自然顺序 对指定列表按升序进行排序。列表中的所有元素都必须实现 Comparable 接口。 2. 此外,列表中的所有元素都必须是可相互比较的(也就是说,对于列表中的任何 e1 和 e2 元素,e1.compareTo(e2) 不得抛出 ClassCastException)。
【示例】
我们打开API查看String类,它是实现了Comparable接口的。所以,我们可以使用字符串对象为例,演示sort排序。
Comparable、Comparator比较器
对象排序,就是比较大小,要实现Comparable或Comparator比较器之一,才有资格做比较排序。
【介绍】
1. Comparable:与对象紧相关的比较器,可以称“第一方比较器”。
2. Comparator:此为与具体类无关的第三方比较器。【示例】
1. 定义一个学生类,实现Comparable接口,使用学号进行Collections.sort排序
2. 定义一个学生类,实现Comparator接口,使用学号进行Collections.sort排序
Map接口
Map用于保存具有映射关系的数据,因此Map集合里保存两组值。
1. 一组值用于保存key,一组值用于保存value
2. key~value之间存在单向一对一关系,通过指定key可以找到唯一的value值
3. key和value都可以是任何引用类型对象
4. 允许存在value为null,但是只允许存在一个key为null
常用方法
方法描述V put(K key, V value)将指定的值与此映射中的指定键关联boolean containsKey(Object key)如果此映射包含指定键的映射关系,则返回trueboolean containsValue(Object value)如果此映射将一个或多个键映射到指定值,则返回trueboolean isEmpty()如果此映射未包含键-值映射关系,则返回trueV get(Object key)返回指定键所映射的值,如果此映射不包含该键的映射关系,则返回nullSet<K> keySet()返回此映射中包含的键的set集合Collection<V> values()返回此映射中包含的值的Collection集合Set<Map.Entry<K,V>> entrySet()返回此映射中包含的映射关系的set集合boolean equals(Object o)返回指定的对象与此映射是否相等int hashCode()返回此映射的哈希码值V remove(Object key)如果存在一个键的映射关系,则将其从此映射中移除void clear()从此映射中移除映射关系int size()返回此映射中的键-值关系数
【示例】
使用水浒传的人物姓名:外号演示map的常用方法
HashMap类
【特点】
1. key无序不可重复 2. 底层是哈希表
【哈希表实现原理】
1. HashMap实际上是一个"链表的数组"的数据结构,每个元素存放链表头结点的数组,即数组和链表的结合体 2. 当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。 3. 只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。 4. 如果这两个Entry的key通过equals比较返回true,新添加Entry的value将覆盖集合中原有Entry的value,但key不会覆盖。
【总结】1. HashMap中key的hashCode值决定了<k,v>键值对组成的entry在哈希表中的存放位置2. HashMap中key通过equals()比较,确定是覆盖key对应的value值还是在链表中添加新的entry 3. 综合前两条,需要重写hashCode()和equals()方法
【示例】
定义学生类,重写hashCode()和equals()方法。放进HashMap中,key是学号,value是学生对象。
【HashSet回顾】
特点
1. HashSet底层由HashMap实现
2. 无序不可重复【示例】
定义5个学生,放进HashSet。(根据学号去重)
TreeMap
TreeMap是SortedMap接口的实现类,可以根据Key进行排序,HashMap没有这个功能。
【特点】
1. 底层由可排序二叉树实现 2. 不指定比较器默认按照key自然升序,指定比较器按照比较器排序
【示例】
定义5个学生对象,放在treeMap中,按照比较器的规则进行排序【总结】
TreeMap需要key实现Comparable接口,排序主要看compareTo()方法。
泛型
泛型是指所操作的数据类型被指定为一个参数,在用到的时候再指定具体的类型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
【示例】
1. 定义一个对象池对象,可以根据需求存放各种类型对象。
class ProjectPool<T>{
private List<T> list = new ArrayList<T>();
public void add(T t){
list.add(t);
}
public int size(){
return list.size();
}
}
2. 集合中使用泛型
List<Student> stuList = new ArrayList<Student>();
【总结】
1. 泛型能更早的发现错误,如类型转换错误 2. 使用泛型,那么在编译期将会发现很多之前要在运行期发现的问题 3. 代码量往往会少一些、运维成本减少 4. 抽象层次上更加面向对象