集合概述

介绍

Java集合是使程序能够存储和操纵元素不固定的一组数据。 所有Java集合类都位于java.uti包中。

【问】:之前我们需要把多个元素放到一起的时候,使用的是数组。那么为何还要提供Java集合工具类呢?
我们通过对比数组和Java集合工具类来解释Java集合工具类的必要性。

数组集合长度固定长度不固定存放任意类型不能存放基本数据类型,只能存放对象的引用

注意: 如果集合中存放基本类型,一定要将其 “装箱”成对应的”基本类型包装类”。

层次结构

Java的集合类主要由两个接口派生而出:Collection和Map。Collection和Map是Java结合框架的根接口,这两个接口又包含了一些子接口或实现类。

Collection的继承层次结构

java项目如何覆盖 apollo 中的配置_数组

Map的继承层次结构

java项目如何覆盖 apollo 中的配置_比较器_02

总结

由以上两图我们可以看出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不会覆盖。

java项目如何覆盖 apollo 中的配置_java_03




【总结】

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. 抽象层次上更加面向对象