Lazy loaded image
学习思考
Lazy loaded imageJavaSE——集合
字数 14989阅读时长 38 分钟
2025-1-25
2025-3-22
type
status
date
slug
summary
tags
category
icon
password

集合

集合使用回顾

  • ArrayList集合存储5个int类型元素
    • ArrayList集合存储5个Person类型元素

      概念

      集合是java中提供的一种容器,可以用来存储多个数据。
      集合和数组的区别:
      • 数组的长度是固定的,集合的长度是可变的
      • 集合中存储的元素必须是引用类型数据

      集合继承关系图

      • ArrayList的继承关系
        • 查看ArrayList类,可以发现它继承抽象类AbstractList的同时实现接口List,而List接口又继承了Collection接口,Collection接口是最顶层集合接口。
      • 集合继承体系
        • 在使用ArrayList类时,该类已经把所有抽象方法进行了重写,实现Collection接口的所有子类都会进行方法重写。
          Collecton接口常用的子接口有:List接口、Set接口。
        • List接口常用的子类有:ArrayList类、LinkedList类
        • Set接口常用的子类有:HashSet类、LinkedHashSet类

      Collection的add方法

      Collection的remove方法

      迭代器

      概述

      Java中提供了很多个集合,它们在存储元素时,采用的存储方式不同,要取出这些集合中的元素,可以通过一种通用的获取方式来完成。
      Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出,这种取出方式专业术语称为迭代。
      每种集合的底层的数据结构不同,例如ArrayList是数组,LinkedList底层是链表,但是无论使用那种集合,都会判断是否有元素以及取出里面的元素的动作,Java提供的迭代器定义了统一的判断元素和取元素的方法。

      实现原理

      Iterator接口定义了两个抽象方法:
      • boolean hasNext():判断集合中还有没有可以被取出的元素,如果有返回true
      • next():取出集合中的下一个元素

      代码实现

      执行过程

      迭代器的原理:
      for循环的迭代写法:

      集合迭代中的转型

      在使用集合时,需要注意以下几点:
      • 集合中存储其实都是对象的地址
      • JDK1.5版本以后集合中可以存储基本数值,因为出现了基本类型包装类,它提供了自动装箱操作(基本类型对象),这样,集合中的元素就是基本数值的包装类对象
      存储时提升了Object。取出时要使用元素的特有内容,必须向下转型。
      注意:如果集合中存放的是多个对象,这时进行向下转型会发生类型转换异常。
      Iterator接口也可以使用<>来控制迭代元素的类型的。

      增强for循环遍历数组

      增强for循环遍历集合

      并发修改异常

      集合和迭代器同时持有同一个对象,当集合在添加,和删除集合元素时(修改呢),迭代器并不知道,所以会发生并发修改异常。
      注意:增强for也会产生并发修改异常。
      如何解决:
      1. 使用普通for循环
      1. 使用listIterator:List特有,其他集合不能使用

      ArrayList

      数组可以保存多个元素,但在某些情况下无法确定到底要保存多少个元素,此时数组将不再适用,因为数组的长度不可变;为了保存这些数目不确定的元素,JDK中提供了一系列特殊的类,这些类可以存储任意类型的元素,并且长度可变,统称为集合。
      ArrayList集合是程序中最常见的一种集合,它属于引用数据类型(类),在ArrayList内部封装了一个长度可变的数组,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组。

      创建对象

      1. 导包:import java.util.ArrayList;
      1. 创建引用类型的变量
        下面给出8种基本数据类型所对应的引用数据类型表示形式:
        基本数据类型
        对应的引用数据类型表示形式
        byte
        Byte
        short
        Short
        Int
        Integer
        long
        Long
        float
        Float
        double
        Double
        char
        Character
        boolean
        Boolean

        常用方法

        方法声明
        功能描述
        boolean add(Object obj)
        将指定元素obj追加到集合的末尾
        Object get(int index)
        返回集合中指定位置上的元素
        int size()
        返回集合中的元素个数
        案例如下:
        强调一点,ArrayList集合相当于是一个长度可变的数组,所以访问集合中的元素也是采用索引方式访问,第一个元素存储在索引0的位置,第二个元素存储在索引1的位置,依次类推。

        遍历

        通过遍历,得到集合中每个元素,这是集合中最常见的操作,集合的遍历与数组的遍历很像,都是通过索引的方式。

        补充方法

        方法声明
        功能描述
        boolean add(int index, Object obj)
        在集合中指定index位置,添加新元素obj
        Object remve(int index)
        从集合中删除指定index处的元素,并返回该元素
        void clear()
        清空集合中所有元素
        Object set(int index, Object obj)
        用指定元素obj替代集合中指定index位置的元素

        随机点名器案例

        案例介绍

        随机点名器,即在全班同学中随机的找出一名同学,打印这名同学的个人信息。它具备以下3个内容:
        • 存储所有同学姓名
        • 总览全班同学姓名
        • 随机点名其中一人,打印到控制台

        需求分析

        对本案例进行分析,得出如下结果:
        1. 存储全班同学信息(姓名、年龄):将容器换成集合,集合中存的是Student类型
        1. 打印全班同学每一个人的信息(姓名、年龄):遍历集合
        1. 在班级总人数范围内,随机产生一个随机数,查找该随机数所对应的同学信息(姓名、年龄)
        随机点名器明确地分为了三个功能,如果将多个独立功能的代码写到一起,则代码相对冗长,可以针对不同的功能可以将其封装到一个方法中,将完整独立的功能分离出来。
        而在存储同学姓名时,如果对每一个同学都定义一个变量进行姓名存储,则会出现过多孤立的变量,很难一次性将全部数据持有;此时,可以采用ArrayList集合来解决多个学生信息的存储问题。

        代码实现

        库存管理案例

        案例介绍

        将原有的库存管理案例,采用更好的集合方式实现。
        对下列功能进行方法封装:
        • 打印库存清单功能
        • 库存商品数量修改功能
        • 退出程序功能

        需求分析

        管理员能够进行的操作有3项(查看、修改、退出),可以采用(switch)菜单的方式来完成;每一项功能操作,采用方法进行封装,增强程序的可读性。

        代码实现

        List接口

        特点

        • 它是一个元素存取有序的集合;例如,存元素的顺序是11、22、33,那么集合中,元素的存储就是按照11、22、33的顺序完成的
        • 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)
        • 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素
        • List接口的常用子类有:
          • ArrayList集合
          • LinkedList集合

        特有方法

        • 增加元素方法
          • add(Object e):向集合末尾处,添加指定的元素
          • add(int index, Object e) 向集合指定索引处,添加指定的元素,原有元素依次后移
        • 删除元素删除
          • remove(Object e):将指定元素对象,从集合中删除,返回值为被删除的元素
          • remove(int index):将指定索引处的元素,从集合中删除,返回值为被删除的元素
        • 替换元素方法
          • set(int index, Object e):将指定索引处的元素,替换成指定的元素,返回值为替换前的元素
        • 查询元素方法
          • get(int index):获取指定索引处的元素,并返回该元素

        迭代器的并发修改异常

        运行上述代码会发生错误java.util.ConcurrentModificationException,这是因为在迭代过程中,使用了集合的方法对元素进行操作。导致迭代器并不知道集合中的变化,容易引发数据的不确定性。
        并发修改异常解决办法:
        在迭代时,不要使用集合的方法操作元素,可以使用ListIterator迭代器操作元素是;ListIterator的出现,解决了使用Iterator迭代过程中可能会发生的错误情况。

        存储结构

        • 栈结构:后进先出/先进后出,FILO(first in last out)
          • 先进后出即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素;例如子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹
          • 栈的入口、出口的都是栈的顶端位置
          • 压栈:就是存元素;即把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置
          • 弹栈:就是取元素;即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置
        • 队列结构:先进先出/后进后出,FIFO(first in first out)
          • 先进先出,即存进去的元素,要在后它前面的元素依次取出后,才能取出该元素;例如安检排成一列,每个人依次检查,只有前面的人全部检查完毕后,才能排到当前的人进行检查
          • 队列的入口、出口各占一侧
        • 数组结构:查询快,通过索引快速找到元素;增删慢,每次增删都需要开辟新的数组,将老数组中的元素拷贝到新数组中,开辟新数组耗费资源
          • 查找元素快:通过索引,可以快速访问指定位置的元素
          • 增删元素慢:
            • 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置
            • 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中
        • 链表结构:查询慢,每次都需要从链头或者链尾找起;增删快,只需要修改元素记录的下个元素的地址值即可,不需要移动大量元素
          • 多个节点之间,通过地址进行连接;例如多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了
          • 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
          • 增删元素快:
            • 增加元素:只需要修改连接下个元素的地址即可
            • 删除元素:只需要修改连接下个元素的地址即可

        ArrayList集合特点

        底层是可变数组,查找快,因为底层有索引,并且是连续。

        LinkedList集合特点

        底层采用链表结构,每次查询都要从链头或链尾找起,查询相对数组较慢,但是删除直接修改元素记录的地址值即可,不要大量移动元素。
        LinkedList的索引决定是从链头开始找还是从链尾开始找;如果该元素小于元素长度一半,从链头开始找起,如果大于元素长度的一半,则从链尾找起。

        LinkedList特有方法

        Vector类特点

        • Vector集合数据存储的结构是数组结构,为JDK中最早提供的集合,它是线程同步的
        • Vector中提供了一个独特的取出方式,就是枚举Enumeration,它其实就是早期的迭代器,此接口Enumeration的功能与Iterator接口的功能是类似的
        • Vector集合已被ArrayList替代,枚举Enumeration已被迭代器Iterator替代

        Set接口

        特点

        • 它是个不包含重复元素的集合
        • Set集合取出元素的方式可以采用:迭代器、增强for
        • Set集合有多个子类,这里介绍HashSet、LinkedHashSet这两个集合

        存储和迭代

        哈希表的数据结构

        加载因子是表中填入的记录数/哈希表的长度,例如哈希表长度是16,加载因子是0.75,代表其中存入16*0.75=12个元素;如果在存入第十三个(>12)元素,导致存储链子过长,会降低哈希表的性能,此时会扩充哈希表(再哈希),底层会开辟一个长度为原长度2倍的数组,把老元素拷贝到新数组中,再把新元素添加数组中。
        存入元素数量>哈希表长度*加载因子就要扩容,因此加载因子决定扩容时机。

        字符串对象的哈希值

        哈希表的存储过程

        存取原理:每存入一个新的元素都要走以下三步:
        1. 首先调用本类的hashCode()方法算出哈希值
        1. 在容器中找是否与新元素哈希值相同的老元素
            • 如果没有直接存入
            • 如果有转到第三步
        1. 新元素会与该索引位置下的老元素利用equals方法一一对比,一旦新元素.equals(老元素)返回true,停止对比,说明重复,不再存入,如果与该索引位置下的老元素都通过equals方法对比返回false,说明没有重复,存入

        哈希表存储自定义对象

        自定义对象重写hashCode和equals

        LinkedHashSet集合

        判断集合元素唯一的原理

        ArrayList、HashSet判断对象是否重复的原理

        ArrayList的contains方法底层依赖于equals方法。
        ArrayList的contains方法会使用调用方法时,传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素;此时,当ArrayList存放自定义类型时,由于自定义类型在未重写equals方法前,判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。

        HashSet的add/contains等方法判断元素是否重复原理

        HashSet的add()方法和contains方法()底层都依赖 hashCode()方法与equals方法()。
        Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。
        HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:
        1. 先判断新元素与集合内已经有的旧元素的HashCode值
          1. 如果不同,说明是不同元素,添加到集合
          2. 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合
        所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcode与equals方法。

        hashCode和equals方法的面试题

        在 Java 应用程序执行期间:
        1. 如果根据equals(Object)方法,两个对象是相等的,那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果
        1. 如果根据equals(java.lang.Object)方法,两个对象不相等,那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果
            • 两个对象不同(对象属性值不同),equals返回false:两个对象调用hashCode()方法哈希值相同
            • 两个对象调用hashCode()方法哈希值不同:equals返回true
            • 两个对象不同(对象属性值不同),equals返回false:两个对象调用hashCode()方法哈希值不同
            • 两个对象调用hashCode()方法哈希值相同:equals返回true
        所以说两个对象哈希值无论相同还是不同,equals都可能返回true。

        Map接口

        概述

        通过查看Map接口,可以发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同:
        • Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储
        • Map中的集合,元素是成对存在的(理解为夫妻),每个元素由键与值两部分组成,通过键可以找对所对应的值
        • Collection中的集合称为单列集合,Map中的集合称为双列集合
        • 需要注意的是,Map中的集合不能包含重复的键,值可以重复,每个键只能对应一个值
        • Map中常用的集合为HashMap集合、LinkedHashMap集合

        常用集合概述

        Map有多个子类,这里主要讲解常用的HashMap集合、LinkedHashMap集合。
        • HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致;由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法
        • LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构;通过链表结构可以保证元素的存取顺序一致,通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法
        注意:Map接口中的集合都有两个泛型变量<K,V>,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量<K,V>的数据类型可以相同,也可以不同。

        常用方法

        keySet方法

        键找值方式:即通过元素中的键,获取键所对应的值。
        操作步骤:
        1. 获取Map集合中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键
        1. 遍历键的Set集合,得到每一个键
        1. 根据键利用get(key)去Map找所对应的值

        Entry

        在Map类设计时,提供了一个嵌套接口Entry;Entry将键值对的对应关系封装成了对象,即键值对对象,这样在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。
        Entry是Map接口中提供的一个静态内部嵌套接口。
        相关方法:
        • getKey()方法:获取Entry对象中的键
        • getValue()方法:获取Entry对象中的值
        • entrySet()方法:用于返回Map集合中所有的键值对(Entry)对象,以Set集合形式返回

        entrySet方法

        键值对方式:即通过集合中每个键值对(Entry)对象,获取键值对(Entry)对象中的键与值。
        操作步骤:
        1. 获取Map集合中,所有的键值对(Entry)对象,以Set集合形式返回
        1. 遍历包含键值对(Entry)对象的Set集合,得到每一个键值对(Entry)对象
        1. 通过键值对(Entry)对象,获取Entry对象中的键与值

        Map集合遍历方式增强for循环

        注意:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。

        HashMap集合存储和遍历

        • 当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode和equals方法
        • 如果要保证map中存放的key和取出的顺序一致,可以使用LinkedHashMap集合来存放

        LinkedHashMap的特点

        Hashtable的特点

        Collections工具类

        集合嵌套

        集合嵌套并不是一个新的知识点,仅仅是集合内容又是集合,如Collection集合嵌套、Collection集合与Map集合相互嵌套、Map集合嵌套。
        • ArrayList嵌套ArrayList
          • Map嵌套ArrayList
            • Map集合嵌套

              keySet遍历

              entrySet遍历

              集合继承体系的面向对象思想

              • 接口:用来明确所有集合中该具有的功能,相当于在定义集合功能标准
              • 抽象类:把多个集合中功能实现方式相同的方法,抽取到抽象类实现,具体集合不再遍写,继承使用即可
              • 具体类:继承抽象类,实现接口,重写所有抽象方法,达到具备指定功能的集合。每个具体集合类,根据自身的数据存储结构方式,对接口中的功能方法,进行不同方式的实现

              斗地主洗牌发牌案例

              案例介绍

              按照斗地主的规则,完成洗牌发牌的动作。
              具体规则:
              1. 组装54张扑克牌
              1. 将54张牌顺序打乱
              1. 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
              1. 查看三人各自手中的牌(按照牌的大小排序)、底牌
              手中扑克牌从大到小的摆放顺序:大王、小王、2、A、K、Q、J、10、9、8、7、6、5、4、3。

              需求分析

              斗地主的功能分析。
              具体规则:
              1. 组装54张扑克牌
              1. 将54张牌顺序打乱
              1. 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
              1. 查看三人各自手中的牌(按照牌的大小排序)、底牌
              需求分析:
              1. 准备牌,完成数字与纸牌的映射关系
                1. 使用双列Map(HashMap)集合,完成一个数字与字符串纸牌的对应关系(相当于一个字典)。
              1. 洗牌,通过数字完成洗牌发牌
              1. 发牌,将每个人以及底牌设计为ArrayList<String>,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌
                  • 存放的过程中要求数字大小与斗地主规则的大小对应
                  • 将代表不同纸牌的数字分配给不同的玩家与底牌
              1. 看牌,通过Map集合找到对应字符展示,通过查询纸牌与数字的对应关系,由数字转成纸牌字符串再进行展示

              代码实现

              首先,要修改java文件编码,由GBK修改为UTF-8,因为默认的字符编码GBK梅花、方片、黑桃、红桃(♠♥♦♣)等特殊字符。

              准备牌

              洗牌

              发牌

              看牌

               
              上一篇
              JavaSE——标准库
              下一篇
              JavaSE——异常、泛型及反射

              评论
              Loading...