集合进阶
集合的体系结构
注:如果想要将基本类型存入集合,需要用基本类型包装类存入
Collection 集合
Collection是一个接口
- Collection是单列集合的顶层接口,它表示一组对象,这些对象也称为Collection元素
- JDK不提供此接口的任何直接实现,他提供更具体的子接口(如Set或List)实现类
创建Collection集合的对象
- 多态的方式
- 具体的实现类 ArrayList
Collection<String> c = new ArrayList<String>();//利用多态创建集合
Collection接口常用方法
方法名 | 说明 |
---|---|
boolean add(E e) | 添加元素 |
boolean remove(Object o) | 从集合中移除指定的元素 |
void clear() | 清空集合中的元素 |
boolean contains(Object o) | 判断集合中是否有指定元素 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合长度,也就是集合中元素个数 |
Collection集合的遍历
Iterator:迭代器,集合的专用遍历方式
-
Iteratoriterator():返回次集合中元素的迭代器,通过集合的ierator方法得到
-
迭代器通过集合的iterator()方法得到,所以说他依赖于集合而存在
Iterator本身是一个接口,在ArrayList中重写了iterator()方法,并重写了内部实现类Itr
iterator()方法的返回值也为此接口的实现类 Itr的对象
Iterator<String> it = c.iterator();//多态的一种用法,引用接口指向实现类对象
有两个常用的调用方法
it.next();//迭代获取元素
it.hasNext();//判断有没有下一个元素
集合使用流程
public class Test {
public static void main(String[] args) {
//创建集合对象
Collection<String> c = new ArrayList<String>();
//创建元素
String s = "Hello";
//添加元素到集合
c.add(s);
//创建迭代器
Iterator<String> it = c.iterator();
//通过迭代器判断是否还有元素
while (it.hasNext()) {
//获取到元素
String s1 = it.next();
//输出元素
System.out.println(s1);
}
}
}
List集合
List接口继承Collection接口
- 有序集合(也称为序列),用户可以精准控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素。
- 与Set集合不同,列表通常允许重复的元素
List接口特有方法
方法名 | 说明 |
---|---|
void add(int index,E e) | 在此集合中的指定位置添加指定元素 |
E remove (int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E e) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引出的元素 |
注:List有一个具体的实现类ArrayList,在其中重写了两个父接口的方法
在ArrayList中,分别重写了List接口中E remove (int index)方法,和Collection接口中boolean remove(Object o)方法。(这里是方法的重写,不是方法的重载)
并发修改异常
ConcurrntModificationException
使用迭代器时需要保证List集合的内容不发生改变,如果List集合中内容发生改变,迭代器又没有重新使用List集合iterator()方法去更新内容。直接调用迭代器的next()方法就会出现并发修改异常。
如果直接使用hasNext()方法则不会出现异常,因为这个方法没有检测判断
源码分析
modCount变量(List的父类抽象类AbstractList中)会在Itr实现类实例化时(调用iterator方法时)赋值给expectedModCount属性。
其意思为集合修改次数,和集合预期修改次数(初始化迭代器时这两个变量相等)
当在寄存器遍历过程中调用add方法,则modCount++;
调用next()方法时检测发现,集合修改次数与集合预期修改次数不相等,则抛出异常
个人理解就是不可以在迭代器遍历集合的过程中对集合添加元素
解决方案
不用迭代器,用for加索引的方式来遍历集合。
ListIterator列表迭代器
列表迭代器
- 通过List接口的listIterator()方法得到,所以说它是List集合特有的迭代器
- 允许沿任意方向遍历列表的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置
ListIterator继承自Iterator接口
ListIterator常用方法
方法名 | 说明 |
---|---|
E next() | 返回迭代中的下一元素 |
boolean hasNext() | 判断列表中是否还有下一元素 |
E previous() | 返回列表中的上一元素 |
boolean hasPrevious() | 判断列表是否还有上一元素 |
void add(E e) | 将指定元素插入列表 |
previous英文释义:以前的、先前的、以往的
独有的add方法
列表迭代器可以调用add()方法直接向集合中添加元素,而不会出现并发修改异常
原因是因为add()方法在添加元素后modCount重新赋值给expectedModCount属性
增强for循环
简化数组和Collection集合的遍历
- 继承Iterable接口的实现类允许对象成为增强for的目标
- 增强for的内部原理是一个Iterator迭代器
格式:
for(数组(集合)中元素的数据类型 变量名 : 数组(集合)){
在此处使用变量即可,该变量就是元素
}
注:增强for同样不可以在遍历时对集合进行添加元素操作
LinkedList集合
LinkedList是List接口的具体实现类,与ArrayList同级别,底层通过链表实现
LinkedList遍历方式与ArrayList基本相同
LinkedList集合特有方法
方法名 | 说明 |
---|---|
void addFirst(E e) | 在该表开头插入指定元素 |
void addLast(E e) | 将指定的元素追加到此列表的表尾 |
E getFirst() | 获取表中第一个元素 |
E getLast() | 获取表中最后一个元素 |
E removeFirst() | 从此表中删除并返回第一个元素 |
E removeLast() | 从此列表中删除并返回最后一个元素 |
Set集合
Set接口是继承于Collection接口
- Set集合中不包含重复元素的集合
- 没有带索引的方法,所以不能使用普通for循环进行遍历
HashSet 实现类
HashSet 实现类是继承于Set接口和Collection接口,在其中重写了Collection接口的方法
HashSet'实现类对集合的迭代顺序不做任何保证
Hash值
哈希值:是JDK根据对象的地址或者字符串或者数字计算出来的int类型的值
object类中的int hashCode()方法可以获取对象的哈希值
默认情况下,不同对象的哈希值是不同的
通过方法重写,可以实现不同对象的哈希值是相同的
HashSet集合的概述和特点
HashSet特点
- 底层数据结构是哈希表
- 对集合的迭代顺序不做任何保证,也就是说不保证存储和取出的元素顺序一致
- Set集合中不包含重复元素的集合
- 没有带索引的方法,所以不能使用普通for循环进行遍历
注:HashSet集合类也继承于Set接口,所以继承Set集合特点
存储流程*
- i获取对象的hash值,计算其地址判断有没有值
- 如果有值,就遍历该位置的所有的元素,判断哈希值是否与新元素相同
- 如果相同,判断元素内容是否相等(equals())
- 如果相等,说明元素重复,不存储
注:HashSet的数据结构用拉链法,也就是说同一个hash值算出的地址可以存储多个元素
保证HashSet元素唯一性
HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法。
- HashSet集合排重时,需要判断两个对象是否相同,对象相同的判断可以通过hashCode值判断,所以需要重写hashCode()方法
- HashSet中不能有相同的元素,放入一个值调用hashCode()(获取位置)是否重复,然后用equals判断是否为重复值。
- 如果只重写其中一个方法的时候,向HashSet集合中添加多个对象时,所有属性都相同时,并没有完成想要的排重效果。
情况一:当我们往HashSet集合中添加 8大基本类型和String类型的时候,不需要重写hashCode()和equals()方法。因为任何对象都是Object类的子类,所以任何对象都拥有这个方法。 情况二:当我们往HashSet集合添加引用数据类型对象的时候,就需要重写hashCode()和equals()方法。建立自己的比较方式,才能保证HashSet集合中的对象唯一。
LinkedHashSet集合的概述和特点
LinkedHashSet集合特点
- 哈希表和链表实现Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 有哈希表保证元素唯一,也就是说没有重复的元素
TreeSet集合的概述和特点
TreeSet集合特点
-
元素有序,这里的顺序不是指元素位置上有序,而是按照元素本身的关系进行排序,排序规则方式取决于构造方法
- TreeSet():根据元素自然排序进行排序
- TreeSet(Comparator comparator):根据指定的比较器进行排序
comparator英文释义:比较器
-
没有带索引的方法,所以不能使用普通for循环进行遍历
-
继承Set集合,不包含重复元素
注:与hash类型集合不同,TreeSet保证元素唯一性的方式是判断差值是否为零
自然排序Comparable的使用
comparable英文释义:类似的,可比较的
compara英文释义:比较
- 用TreeSet集合存储自定义对象时,无参构造方法使用的是自然排序对元素进行排序
- 自然排序,就是让元素所属的类实现Comparable接口,重写**comparaTo(T o)**方法
- 重写方法时,一定要注意排序规则必须按照主要条件和次要条件来写
@Override
public int compareTo(Student o) {
//return 1;
//假如这里的形参是s1 o.age==s1.age this.age==s2.age
//如果想象成排序二叉树,就是-1在左子树,1在右子树,0不存
int num = this.age - o.age;//升序方法
//在String类中,重写了自然排序方法,这里可以直接用
int num1 = num == 0 ? this.name.compareTo(o.name) : num;
return num1;
}
比较器排序Comparator的使用
- 用TreeSet集合存储自定义对象时,带参构造方法使用的是比较器排序对元素进行排序
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写**compara(T o1,T o2)**方法
- 重写方法时,一定要注意排序规则必须按照主要条件和次要条件来写
注:在TreeSet实例化时,带参构造方法的实参应该为Comparator类的对象
public class Test {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int num = o1.getAge() - o2.getAge();
int num1 = num == 0 ?o1.getName().compareTo(o2.getName()) : num;
return num1;
}
});//这里的实参使用了匿名内部类作为接口的实现类对象
}
}