集合

发布时间 2023-06-30 16:04:02作者: lawranceCha

1、集合的概述

  • 集合和数组都是容器

1.1、数组的特点

  • 数组定义完成并启动后,类型确定、长度固定。
  • 适合元素的个数和类型确定的业务场景,不适合做需要增删数据操作。

1.2、集合的特点

  • 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。
  • 集合非常适合做元素的增删操作。
  • 集合是Java中储存对象数据的一种容器

1.3、总结

数组和集合的元素存储的个数问题。

  • 数组定义后类型确定,长度固定
  • 集合类型可以不固定,大小是可变的。

数组和集合存储元素的类型问题。

  • 数组可以存储基本类型和引用类型的数据。
  • 集合只能存储引用数据类型的数据。

数组和集合适合的场景

  • 数组适合做数据个数和类型确定的场景。
  • 集合适合做数据个数不确定,且要做增删元素的场景。

2、集合的体系特点

2.1、集合类体系结构

  • Collection单列集合,每个元素(数据)只包含一个值。
  • Map双列集合,每个元素包含两个值(键值对)。
  • 注意:前期先掌握Collection集合体系的使用。

2.2、Collection集合体系

2.3、Collection集合特点

  • List系列集合:添加的元素是有序、可重复、有索引。
    • ArrayList、LinekdList :有序、可重复、有索引。
  • Set系列集合:添加的元素是无序、不重复、无索引。
    • HashSet: 无序、不重复、无索引;
    • LinkedHashSet: 有序、不重复、无索引。
    • TreeSet:按照大小默认升序排序、不重复、无索引。

2.4、集合对于泛型的支持

  • 集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型
Collection<String> lists = new ArrayList<String>();
Collection<String> lists = new ArrayList<>(); 
// JDK 1.7开始后面的泛型类型申明可以省略不写

注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。

Collection<int> lists = new ArrayList<>();×

以上写法不正确

如果集合中要存储基本类型的数据怎么办?

// 存储基本类型使用包装类
Collection<Integer> lists = new ArrayList<>();
Collection<Double> lists = new ArrayList<>();

2.5、总结

集合的代表是?

  • Collection接口。

Collection集合分了哪2大常用的集合体系?

  • List系列集合:添加的元素是有序、可重复、有索引。
  • Set系列集合:添加的元素是无序、不重复、无索引。

如何约定集合存储数据的类型,需要注意什么?

  • 集合支持泛型。
  • 集合和泛型不支持基本类型,只支持引用数据类型。

3、Collection的常用方法

3.1、Collection集合

  • Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。

3.2、Collection API

方法名称 说明
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() 把集合中的元素,存储到数组中

4、集合的遍历方式

4.1、方式一:迭代器

4.1.1、迭代器遍历概述

  • 遍历就是一个一个的把容器中的元素访问一遍。
  • 迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式。

4.1.2、Collection集合获取迭代器

方法名称 说明
Iterator iterator() 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引

4.1.3、Iterator中的常用方法

方法名称 说明
boolean hasNext() 询问当前位置是否有元素存在,存在返回true ,不存在返回false
E next() 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。

4.1.4、迭代器执行流程

4.1.5、总结

迭代器的默认位置在哪里。

  • Iterator iterator():得到迭代器对象,默认指向当前集合的索引0

迭代器如果取元素越界会出现什么问题。

  • 会出现NoSuchElementException异常。

4.2、方式二:foreach/增强for循环

4.2.1、增强for循环

for(元素数据类型 变量名 : 数组或者Collection集合) {
         //在此处使用变量即可,该变量就是元素
}
  • 增强for循环:既可以遍历集合也可以遍历数组。
  • 它是在JDK5之后出现的,其内部原理是一个Itrator迭代器,遍历集合相当于是迭代器的简化写法。
  • 实现Iterable接口的类才可以使用迭代器和增强for,Collection接口已经实现了Iterable接口。
举例说明
Collection<String> list = new ArrayList<>();
...
for(String ele : list) {
System.out.println(ele);
}

4.2.2、增强for修改变量

4.2.3、总结

增强for可以遍历哪些容器?

  • 既可以遍历集合也可以遍历数组。

增强for的关键是记住它的遍历格式

for(元素数据类型 变量名 : 数组或者Collection集合) {
         //在此处使用变量即可,该变量就是元素
}

4.3、方式三:lambda表达式

4.3.1、Lambda表达式遍历集合

  • 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。

4.3.2、Collection结合Lambda遍历的API

方法名称 说明
default void forEach(Consumer<? super T> action): 结合lambda遍历集合

5、集合存储自定义类型的对象

详细内容见相关PPT

总结

集合中存储的是元素的什么信息?

  • 集合中存储的是元素对象的地址。

6、List系列集合

6.1、List集合特点、特有API

6.1.1、List系列集合特点

  • ArrayList、LinekdList :有序,可重复,有索引。
  • 有序:存储和取出的元素顺序一致
  • 有索引:可以通过索引操作元素
  • 可重复:存储的元素可以重复

6.1.2、List集合特有方法

  • List集合因为支持索引,所以多了很多索引操作的独特api,其他Collection的功能List也都继承了。

  • 方法名称 说明
    void add(int index,E element) 在此集合中的指定位置插入指定的元素
    E remove(int index) 删除指定索引处的元素,返回被删除的元素
    E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
    E get(int index) 返回指定索引处的元素

6.1.3、总结

List系列集合特点

  • ArrayList、LinekdList :有序,可重复,有索引。

List的实现类的底层原理

  • ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢。
  • LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的。

6.2、List集合的遍历方式小结

List集合的遍历方式有几种?

  1. 迭代器
  2. 增强for循环
  3. Lambda表达式
  4. for循环(因为List集合存在索引)
List<String> lists = new ArrayList<>();
        lists.add("java1");
        lists.add("java2");
        lists.add("java3");

        /** (1)for循环。 */
        System.out.println("-----------------------");

        for (int i = 0; i < lists.size(); i++) {
            System.out.println(lists.get(i));
        }


        /** (2)迭代器。 */
        System.out.println("-----------------------");
        Iterator<String> it = lists.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }

        /** (3)foreach */
        System.out.println("-----------------------");
        for (String ele : lists) {
            System.out.println(ele);
        }

        /** (4)JDK 1.8开始之后的Lambda表达式  */
        System.out.println("-----------------------");
        lists.forEach(s -> System.out.println(s) );

6.3、ArrayList集合的底层原理

  • ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。

  • 第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。

  • List<String> list = new ArrayList<>();
    list.add("a");
    

6.4、LinkedList集合特点

6.4.1、LinkedList的特点

  • 底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。

6.4.2、LinkedList集合的特有功能

方法名称 说明
public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素

7、集合的并发修改异常问题

7.1、问题引出

  • 当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。

7.2、哪些遍历存在问题

  • 迭代器遍历集合且直接用集合删除元素的时候可能出现。

  • 迭代器在遍历的时候不允许对集合做增删,因为当元素被删除时,其后一个元素会取代被删元素的位置,而检索游标则会直接跳至下一个位置,如果取代者也是要删除的元素,则会导致崩溃。

  • 增强for循环遍历集合且直接用集合删除元素的时候可能出现,且无法解决。

  • foreach(增强for循环)遍历删除 (会出现问题,这种无法解决的,foreach不能边遍历边删除,会出bug)
    
  • lambda表达式(会出现问题,这种无法解决的,Lambda遍历不能边遍历边删除,会出bug)
    

7.3、哪种遍历且删除元素不出问题

  • 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。

  •  // a、迭代器遍历删除
            Iterator<String> it = list.iterator();
            while (it.hasNext()){
                String ele = it.next();
                if("Java".equals(ele)){
                    // 删除Java
                     list.remove(ele); // 集合删除会出毛病
                    //it.remove(); // 删除迭代器所在位置的元素值(没毛病)
                }
            }
            System.out.println(list);
    
  • 因此要使用迭代器自带的remove()方法,不能直接对集合进行操作

  • 使用for循环遍历并删除元素不会存在这个问题(倒着删)。

  • // d、for循环(边遍历边删除集合没毛病,但是必须从后面开始遍历删除才不会出现漏掉应该删除的元素)
    for (int i = list.size() - 1; i >= 0 ; i--) {
        String ele = list.get(i);
        if("Java".equals(ele)){
            list.remove(ele);
        }
    }
    System.out.println(list);
    

8、泛型深入

8.1、泛型的概述和优势

8.1.1、泛型概述

  • 泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
  • 泛型的格式:<数据类型>; 注意:泛型只能支持引用数据类型。
  • 集合体系的全部接口和实现类都是支持泛型的使用的。

8.1.2、泛型的好处

  • 统一数据类型。
  • 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。

8.1.3、泛型在各处的定义

  • 类后面 → 泛型类
  • 方法申明上 → 泛型方法
  • 接口后面 → 泛型接口

8.2、自定义泛型类

8.2.1、泛型类的概述

  • 定义类时同时定义了泛型的类就是泛型类。

  • 泛型类的格式:修饰符 class 类名<泛型变量>{ }

  • public class MyArrayList<T> {  }
    
  • 此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。

  • 作用:编译阶段可以指定数据类型,类似于集合的作用。

8.2.2、泛型类的原理

  • 把出现泛型变量的地方全部替换成传输的真实数据类型。

8.2.3、总结

泛型类的核心思想

  • 把出现泛型变量的地方全部替换成传输的真实数据类型

泛型类的作用

  • 编译阶段约定操作的数据的类型,类似于集合的作用。

8.3、自定义泛型方法

8.3.1、泛型方法的概述

  • 定义方法时同时定义了泛型的方法就是泛型方法。

  • 泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}

  •  public <T> void show(T t) {};
    
  • 作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。

  • 实例(使用泛型方法实现Arrays.toString(数组)的功能)

  • public static <T> void printArray(T[] arr){
            if(arr!=null)
            {   StringBuilder s=new StringBuilder("[");
                for (int i=0;i< arr.length;i++) {
                    s.append(arr[i]).append(i!=(arr.length-1)?", ":" ");
                }
                s.append("]");
                System.out.println(s);
            }
            else
            {
                System.out.println(arr);
            }
    
        }
    

8.3.2、泛型方法的原理

  • 把出现泛型变量的地方全部替换成传输的真实数据类型。

  • public static <T> T[] getArray(T[] arr){
            return arr;
        }//对同类型的数组复制粘贴
    

8.3.3、总结

泛型方法的核心思想

  • 把出现泛型变量的地方全部替换成传输的真实数据类型

泛型方法的作用

  • 方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性

8.4、自定义泛型接口

8.4.1、泛型接口的概述

  • 使用了泛型定义的接口就是泛型接口。

  • 泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}

  • public interface Data<E>{}
    
  • 作用:泛型接口可以让实现类选择当前功能需要操作的数据类型

8.4.2、泛型接口的原理

  • 实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。

8.4.3、泛型接口的作用

  • 泛型接口可以约束实现类,实现类可以在实现接口的时候传入自己操作的数据类型这样重写的方法都将是针对于该类型的操作。

8.5、泛型通配符、上下限

8.5.1、通配符:?

  • ? 可以在“使用泛型”的时候代表一切类型。
  • E T K V 是在定义泛型的时候使用的。

注意

  • 虽然BMW和BENZ都继承了Car但是ArrayList和ArrayList与ArrayList没有关系的!!

8.5.2、泛型的上下限

  • ? extends Car: ?必须是Car或者其子类 泛型上限
  • ? super Car : ?必须是Car或者其父类 泛型下限

9、Set系列集合

9.1、Set系列集系概述

9.1.1、Set系列集合特点

  • 无序:存取顺序不一致
  • 不重复:可以去除重复
  • 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。

9.1.2、Set集合实现类特点

  • HashSet : 无序、不重复、无索引。
  • LinkedHashSet:有序(添加顺序为队列式)、不重复、无索引。
  • TreeSet:排序(天然的对其中元素进行大小升序排序)、不重复、无索引。

Set集合的功能上基本上与Collection的API一致。

9.1.3、小节

Set系列集合的特点。

  • 无序、不重复、无索引。

Set集合的实现类特点。

  • HashSet无序、不重复、无索引。
  • LinkedHashSet 有序、不重复、无索引。
  • TreeSet 可排序、不重复、无索引。

9.2、HashSet元素无序的底层原理:哈希表

9.2.1、HashSet底层原理

  • HashSet集合底层采取哈希表存储的数据。
  • 哈希表是一种对于增删改查数据性能都较好的结构。

9.2.2、哈希表的组成

  • JDK8之前的,底层使用数组+链表组成
  • JDK8开始后,底层采用数组+链表+红黑树组成。

哈希值

  • 是JDK根据对象的地址,按照某种规则算出来的int类型的数值。

Object类的API

public int hashCode();返回对象的哈希值

对象的哈希值特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
  • 默认情况下,不同对象的哈希值是不同的。

HashSet1.7版本原理解析:数组 + 链表 +(结合哈希算法)

  1. 创建一个默认长度16,默认加载因子为0.75的数组,数组名table

    Set<String> sets = new HashSet<>();
    
  2. 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)

  3. 判断当前位置是否为null,如果是null直接存入

  4. 如果位置不为null,表示有元素,则调用equals方法比较

  5. 如果一样,则不存,如果不一样,则存入数组,

    • JDK 7新元素占老元素位置,指向老元素

    • JDK 8中新元素挂在老元素下面

    • 当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍

  6. 结论:哈希表是一种对于增删改查数据性能都较好的结构。

JDK1.8版本开始HashSet原理解析

  • 底层结构:哈希表(数组、链表、红黑树的结合体)
  • 当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。
  • 结论:JDK8开始后,哈希表对于红黑树的引入进一步提高了操作数据的性能。

9.2.3、小节

Set集合的底层原理是什么样的?

  • JDK8之前的,哈希表:底层使用数组+链表组成
  • JDK8开始后,哈希表:底层采用数组+链表+红黑树组成。

9.3、HashSet元素去重复的底层原理

9.3.1、HashSet去重复原理解析

  • 创建一个默认长度16的数组,数组名table

  • 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)

  • 判断当前位置是否为null,如果是null直接存入

  • 如果位置不为null,表示有元素,则调用equals方法比较

  • 如果一样,则不存,如果不一样,则存入数组。

  • 结论:如果希望Set集合认为2个内容一样的对象是重复的,
    必须重写对象的hashCode()和equals()方法

  • 重写之后算的是对象属性的哈希值,重写之前算的是对象本身地址的哈希值

  •  @Override//重写后
        public int hashCode() {
            return Objects.hash(name, age, sex);
        }
    
    public int hashCode() {
        // 重写前的hashCode源码
        int h = hash;//哈希值
        if (h == 0 && !hashIsZero) {
    		h = isLatin1() ? StringLatin1.hashCode(value):StringUTF16.hashCode(value);     
            if (h == 0) {
                hashIsZero = true;
            } else {
                hash = h;
            }
        }
        return h;
    }
    
  • 原因:HashSet去重复的原理是先判断哈希值是否相同,只有当地址(哈希值)相同时才会比较该对象值是否和该链表中值存在重复现象,当某个值和某链表中值相同,但其哈希值不同时,HashSet会认为这两个值不一样,从而导致值重复现象出现

9.3.2、实例

需求:

  • 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象

分析

  • 定义学生类,创建HashSet集合对象, 创建学生对象
  • 把学生添加到集合
  • 在学生类中重写两个方法,hashCode()和equals(),自动生成即可
  • 遍历集合(增强for)

9.3.3、小节

如果希望Set集合认为2个内容相同的对象是重复的应该怎么办?

  • 重写对象的hashCode和equals方法。

9.4、实现类:LinkedHashSet

9.4.1、LinkedHashSet集合概述和特点

  • 有序、不重复、无索引。
  • 这里的有序指的是保证存储和取出的元素顺序一致
  • 原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

9.4.2、小节

  • LinkedHashSet集合的特点和原理是怎么样的?
  • 有序、不重复、无索引
  • 底层基于哈希表,使用双链表记录添加顺序。
  • HashSet补上了链表查询慢的短板,而双链补上了HashSet无序的短板。

9.5、实现类:TreeSet

9.5.1、TreeSet集合概述和特点

  • 不重复、无索引、可排序
  • 可排序:按照元素的大小默认升序(有小到大)排序。
  • TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
  • 注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。

9.5.2、TreeSet集合默认的规则

  • 对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
  • 对于字符串类型:默认按照首字符的编号升序排序(大写字母<小写字母<汉字)。
  • 对于自定义类型如Student对象,TreeSet无法直接排序。
  • 结论:想要使用TreeSet存储自定义类型,需要制定排序规则

9.5.3、自定义排序规则

  • TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则

方式一

  • 让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。

  • /**
     * 方法一;类自定义比较规则
     * o1.compareTo(o2)
     * @param o
     * @return
     */
    @Override
    public int compareTo(Apple o) {
        //按照重量进行比较
        return this.weight-o.weight;//会去掉多余重复的
        //return this.weight-o.weight>=0?1:-1;保留重量重复的
    }
    

方式二

  • TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。

  •  /**
             * 方式二 集合自带的比较器对象进行规则制定
             */
            Set<Apple>apples=new TreeSet<>(new Comparator<Apple>() {
                @Override
                public int compare(Apple o1, Apple o2) {
    //                return o1.getWeight()-o2.getWeight();//升序
    //                    return o2.getWeight()- o1.getWeight();//降序
                    //浮点型计算建议使用Double.compare();进行比较
                    return Double.compare(o2.getPrice(),o1.getPrice());
                }
            });
    -------------------------------------------------
    /**
             * 方式二 集合自带的比较器对象进行规则制定
             */
            Set<Apple>apples=new TreeSet<>(( o1, o2)->Double.compare(o2.getPrice(),o1.getPrice())); 							 			
    //                return o1.getWeight()-o2.getWeight();//升序
    //                    return o2.getWeight()- o1.getWeight();//降序
                    //浮点型计算建议使用Double.compare();进行比较
    

两种方式中,关于返回值的规则

  • 如果认为第一个元素大于第二个元素返回正整数即可。
  • 如果认为第一个元素小于第二个元素返回负整数即可。
  • 如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
  • 注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。

9.5.4、小节

TreeSet集合的特点是怎么样的?

  • 可排序、不重复、无索引
  • 底层基于红黑树实现排序,增删改查性能较好

TreeSet集合自定义排序规则有几种方式

  • 2种。
  • 类实现Comparable接口,重写比较规则。
  • 集合自定义Comparator比较器对象,重写比较规则。

练习

需求:
  • 键盘录入3个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低输出到控制台
分析
  • 定义学生类
  • 创建TreeSet集合对象,通过比较器排序进行排序
  • 创建学生对象
  • 把学生对象添加到集合
  • 遍历集合

10、Collection体系的特点、使用场景总结

如果希望元素可以重复,又有索引,索引查询要快?

  • 用ArrayList集合,基于数组的。(用的最多)

如果希望元素可以重复,又有索引,增删首尾操作快?

  • 用LinkedList集合,基于链表的。

如果希望增删改查都快,但是元素不重复、无序、无索引。

  • 用HashSet集合,基于哈希表的。

如果希望增删改查都快,但是元素不重复、有序、无索引。

  • 用LinkedHashSet集合,基于哈希表和双链表。

如果要对对象进行排序。

  • 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。

11、集合工具类Collections

11.1、Collections集合工具类

  • java.utils.Collections:是集合工具类
  • 作用:Collections并不属于集合,是用来操作集合的工具类。

11.2、Collection常用API

方法名称 说明
public static boolean addAll(Collection<? super T> c, T... elements) 给集合对象批量添加元素
public static void shuffle(List<?> list) 打乱List集合元素的顺序

11.3、Collections排序相关API

  • 使用范围:只能对于List集合的排序。

排序方式1

方法名称 说明
public static void sort(List list) 将集合中元素按照默认规则排序

注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口。

排序方式2

方法名称 说明
public static void sort(List list,Comparator<? super T> c) 将集合中元素按照指定规则排序

11.4.思考

为何Collections的API只能针对于List集合排序?

方法名称
public static void sort(List list)
public static void sort(List list,Comparator<? super T> c)

12、Map集合体系

说明
  • 使用最多的Map集合是HashMap。
  • 重点掌握HashMap , LinkedHashMap , TreeMap。其他的后续理解。

12.1、Map集合的概述

12.1.1.Map集合概述和使用

  • Map集合是一种双列集合,每个元素包含两个数据。
  • Map集合的每个元素的格式:key=value(键值对元素)。
  • Map集合也被称为“键值对集合”。

12.1.2、Map集合整体格式

  • Collection集合的格式: [元素1,元素2,元素3..]
  • Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}

Map集合的使用场景之一:购物车系统

分析
  • 购物车提供的四个商品和购买的数量在后台需要容器存储。
  • 每个商品对象都一一对应一个购买数量。

  • 商品对象看成是Map集合的建,购买数量看成Map集合的值。

12.1.3、小节

Map集合是什么?使用场景是什么样的?

  • Map集合是键值对集合
  • Map集合非常适合做类购物车这样的业务场景。

12.2、Map集合体系特点

  • Map集合的键无序、不重复的
  • Map集合的值值不做要求可以重复

12.2.1、Map集合体系特点

  • Map集合的特点都是由键决定的。
  • Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
  • Map集合后面重复的键对应的值会覆盖前面重复键的值。
  • Map集合的键值对都可以为null。

12.2.2、Map集合实现类特点

  • HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
  • LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
  • TreeMap:元素按照键是排序,不重复,无索引的,值不做要求。
  • 添加元素的时候,如果出现键重复的情况,会使后添加的元素中的值替换掉原来的值。

12.2.3、小节

Map集合的特点

  • HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
  • LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
  • TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。

12.3、Map集合常用API

12.3.1、Map集合

  • Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。

12.3.2、Map API

方法名称 说明
V put(K key,V value) 添加元素
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数

12.4、Map集合的遍历方式

  • 方式一:键找值的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
  • 方式二:键值对的方式遍历,把“键值对“看成一个整体,难度较大。
  • 方式三:JDK 1.8开始之后的新技术:Lambda表达式。

Map集合的遍历方式一:键找值

  • 先获取Map集合的全部键的Set集合。
  • 遍历键的Set集合,然后通过键提取对应值。

键找值涉及到的API

方法名称 说明
Set keySet() 获取所有键的集合
V get(Object key) 根据键获取值
// 1、键找值:第一步:先拿到集合的全部键。
        Set<String>keys=maps.keySet();
        // 2、第二步:遍历每个键,根据键提取值
        for (String key : keys) {
            int value=maps.get(key);
            System.out.println(key+" × "+value);
        }

Map集合的遍历方式二:键值对

  • 先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。
  • Set集合中装的实际上为Map的实验类对象而Map可以看成一个接口
  • 遍历Set集合,然后提取键以及提取值。

键值对涉及到的API

方法名称 说明
Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
K getKey() 获得键
V getValue() 获取值
// 1、把Map集合转换成Set集合
        Set<Map.Entry<String, Integer>> entries = maps.entrySet();
        // 2、开始遍历
        for (Map.Entry<String, Integer> entry : entries) {
            System.out.println(entry.getKey()+" × "+entry.getValue());
        }

Map集合的遍历方式三:lambda表达式

  • 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。

Map结合Lambda遍历的API

方法名称 说明
default void forEach(BiConsumer<? super K, ? super V> action) 结合lambda遍历Map集合
//  maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
        maps.forEach(new BiConsumer<String, Integer>() {
            @Override
            public void accept(String key, Integer value) {
                System.out.println(key+" × "+value);
            }
        });//简化前
-----------------------------------------------------
 //  maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
 maps.forEach((k,v) ->System.out.println(k+" × "+v));
//简化后

案例

需求

​ 某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。

分析
  • 将80个学生选择的数据拿到程序中去。

  • 定义Map集合用于存储最终统计的结果。

  • 遍历80个学生选择的数据,看Map集合中是否存在,不存在存入“数据=1“,存在则其对应值+1,

  •  // 1、把80个学生选择的数据拿进来。
            String[] selects={"A","B","C","D"};
            Random r=new Random();
            StringBuilder sb=new StringBuilder();
            for (int i = 0; i < 80; i++) {
                sb.append(selects[(r.nextInt(selects.length))]);
            }
            System.out.println(sb);
    
    
            // 2、定义一个Map集合记录最终统计的结果: A=30 B=20 C=20 D=10  键是景点 值是选择的数量
            Map<Character,Integer>infos=new HashMap<>();//[]
            // 3、遍历80个学生选择的数据
            for (int i = 0; i < sb.length(); i++) {
                // 4、提取当前选择景点字符
                char ch=sb.charAt(i);
                // 5、判断Map集合中是否存在这个键
                if(infos.containsKey(ch))
                {   // 让其值 + 1
                    infos.put(ch,infos.get(ch)+1);
                }else
                {   // 说明此景点是第一次被选
                    infos.put(ch,1);
                }
            }
            // 4、输出集合
            System.out.println(infos);
    

12.5、Map集合的实现类HashMap

12.5.1、HashMap的特点

  • HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
  • 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。
  • HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已。
  • 实际上:Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已
public HashSet() {
map = new HashMap<>();
}

12.5.2、小节

HashMap的特点和底层原理

  • 由键决定:无序、不重复、无索引。HashMap底层是哈希表结构的。
  • 依赖hashCode方法和equals方法保证键的唯一。

如果键要存储的是自定义对象,需要重写hashCode和equals方法。

  • 基于哈希表。增删改查的性能都较好。

案例

需求

​ 创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)。存储三个键值对元素,并遍历

思路
  • 定义学生类
  • 创建HashMap集合对象
  • 创建学生对象
  • 把学生添加到集合
  • 遍历集合

12.6、Map集合的实现类LinkedHashMap

LinkedHashMap集合概述和特点

  • 由键决定:有序、不重复、无索引。
  • 这里的有序指的是保证存储和取出的元素顺序一致
  • 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。

12.7、Map集合的实现类TreeMap

12.7.1、TreeMap集合概述和特点

  • 由键决定特性:不重复、无索引、可排序
  • 可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
  • 注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
  • TreeMap跟TreeSet一样底层原理是一样的。

12.7.2、TreeMap集合自定义排序规则有2种

  • 类实现Comparable接口,重写比较规则。
  • 集合自定义Comparator比较器对象,重写比较规则。

12.7.3、小节

TreeMap集合的特点是怎么样的?

  • 根据键可排序、不重复、无索引
  • 底层基于红黑树实现排序,增删改查性能较好

TreeMap集合自定义排序规则有几种方式2种。

  • 类实现Comparable接口,重写比较规则。
  • 集合自定义Comparator比较器对象,重写比较规则。

案例

需求(1)
  • 创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String)。
  • 学生属性姓名和年龄,按照年龄进行排序并遍历。
思路
  • 定义学生类
  • 创建TreeMap集合对象
  • 创建学生对象
  • 把学生添加到集合
  • 遍历集合
需求(2)

字符串“aababcabcdabcde”
请统计字符串中每一个字符出现的次数,并按照以下格式输出
输出结果:
a(5)b(4)c(3)d(2)e(1)

思路
  • Map集合中,键存字符,值存出现的次数
  • 遍历字符串,得到每一个字符
  • 到集合中看是否包含这个字符
  • 如果不包含,表示是第一次出现
  • 如果包含,表示不是第一次出现

13、集合的嵌套

Map集合案例-统计投票人数

需求

某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生可以选择多个景点,请统计出最终哪个景点想去的人数最多。

分析

将80个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。
定义Map集合用于存储最终统计的结果。