集合进阶

集合的体系结构

1-1663752498592

注:如果想要将基本类型存入集合,需要用基本类型包装类存入

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集合特点

存储流程*
  1. i获取对象的hash值,计算其地址判断有没有值
  2. 如果有值,就遍历该位置的所有的元素,判断哈希值是否与新元素相同
  3. 如果相同,判断元素内容是否相等(equals())
  4. 如果相等,说明元素重复,不存储

注:HashSet的数据结构用拉链法,也就是说同一个hash值算出的地址可以存储多个元素

保证HashSet元素唯一性

HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法。

  1. HashSet集合排重时,需要判断两个对象是否相同,对象相同的判断可以通过hashCode值判断,所以需要重写hashCode()方法
  2. HashSet中不能有相同的元素,放入一个值调用hashCode()(获取位置)是否重复,然后用equals判断是否为重复值。
  3. 如果只重写其中一个方法的时候,向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;
            }
        });//这里的实参使用了匿名内部类作为接口的实现类对象
    }
}