四,java集合框架

一,集合的体系结构

1.1 集合介绍

为什么需要集合?

  • 数组的局限性

    • 长度固定,无法动态扩容

    • 只能存储同一类型数据

    • 缺少现成的操作方法(如排序、查找)

  • 集合的优势

    • 动态扩容(自动调整大小)

    • 可存储对象(包括不同类型)

    • 提供丰富的操作方法(增删改查、排序等)

集合主要分为两类:

  • Collection单列集合

    单列集合:在添加数据的时候每次只能添加一个据的集合

  • Map双列集合

    双列集合:在添加数据的时候每次可以添加两个数据的集合

示意图如下:

1.2 Collection体系

Collection集合体系如下图:

  1. List系列集合:添加的元素是有序的,可重复的,有索引的

    有序:存放和拿取的顺序一致

    可重复:集合中存储得元素可重复

    有索引:可以通过索引获取集合中的每一个元素

  2. Set系列集合:添加的元素是无序,不重复,没有索引的

    无序:存放和拿取得顺序不一致

    不重复:集合中得元素是不可重复

    没有索引:不能通过索引获取集合中的每一个元素

  3. Queue(队列)

二,Collection

Collection是单列集合的最终父级接口,它的功能是全部单列集合都可以继承使用的, 即Collection接口存放的方法都是所有单列集合中共有的方法

Collection常用方法

方法名 说明
public boolean add(E e) 把给定的对象添加到当前集合中
public void clear() 清空集合中的所有元素
public boolean remove(E e) 把给定的对象在当前集合中删除,删除失败返回false
public boolean contains(Object obj) 判断当前集合中是否包含给定的对象
public boolean isEmpty() 判断当前集合是否为空
public int size() 返回集合中元素的个数/集合的长度

注意要点:

  1. Collection是接口,不能创建这玩意的对象而是创建它的实现类的对象

  2. add方法中

    如果我们往List系列添加元素那么返回值永远为true

    如果我们往Set系列添加元素并且这个元素集合中已经存在,那么会返回false

  3. contains 方法在底层是依赖equals方法判断对象是否一致的

    如果我们是自己定义的对象并且我们没有在这个对象中重写equals方法,那么默认使用Object类中的equals方法进行判断,而Object方法中的equals方法是判断地址值的。

    解决方法:在自定义的类中重写equals方法即可

    java 复制代码
    import java.util.ArrayList;
    import java.util.Collection;
    public class Main {
        public static void main(String[] args){
            Collection<User> arr=new ArrayList<>();
            User u1=new User(1,2);
            User u2=new User(3,4);
            arr.add(u1);
            //如果没有在User类中重写equals方法那么调用的就是Object类中的equals就返回false
            System.out.println(arr.contains(u2));
        }
    }

三,Collection系列集合的遍历方式

之所以用新的遍历方式是因为在Collection系列集合中存在Set系列集合,Set系列集合的特点有无索引!故使用新的遍历方式。

3.1 迭代器遍历

迭代器最大的特点:不依赖索引

迭代器在Java中的类是Iterator迭代器是集合专用的遍历方式

  • Collection集合获取迭代器

    java 复制代码
    ArrayList<String> list=new ArrayList<>();
    Iterator<String> it=list.iterator();
  • Iterator中常用的方法

    方法名 说明
    boolean hasNext() 判断当前位置是否有元素,有元素返回true,没有返回false
    E next() 获取当前位置的元素,并且迭代器对象移动到下一个位置
    void remove() 删除当前迭代器指向的元素
  • 举例

    java 复制代码
    ArrayList<String> list=new ArrayList<>();
    list.add("a");
    list.add("aa");
    list.add("aaa");
    list.add("aaaa");
    Iterator<String> it=list.iterator();//获取当前集合的Iterator对象,需要注意迭代器的E和集合的E要一致
    
    while(it.hasNext()){//hasNext是判断当前位置(初始为0)是否有元素!!
    	System.out.println(it.next());//next是获取当前位置的元素的内容!并将迭代器移动到下一个(0->1)
    }

    底层就链表,迭代器可以看做c语言的指针。

    注意事项:

    1. 如果迭代器已经获取完了最后一个元素,我们再次指向next方法,会报错NoSunchElementException

    2. 迭代器遍历完,指针不会复位!

      如果还想再次遍历只能再次获取迭代器对象(因为底层是单向链表

    3. 循环中只能使用一个next方法

    4. 迭代遍历的时候,不能用集合的方式进行增加或删除!

3.2 增强for遍历

介绍:

  • 增强for的底层就是迭代器,为了简化迭代器的书写!
  • 它是在JDK5之后出现的,其内部原理就是一个Iterator迭代器
  • 所有的单列集合数组才能使用增强for进行遍历

格式:

java 复制代码
for(元素类型 变量名 : 数组或集合){
    
}
for(String s:list){
    sout(s);
}
//s其实就是一个第三方变量,在循环的过程中依次表示集合中的每一个数据
//idea可以使用 集合名.for来快捷键生成

小细节: 修改增强for中的变量,不会改变集合中原本的数据

java 复制代码
for(String s:list){
    s="q";
}
//list内容不会改变!

3.3 Lambda表达式遍历

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

方法名 说明
default void forEach(Consumer<? super T> action): 结合lambda遍历集合
java 复制代码
ArrayList<String> list=new ArrayList<>();
list.add("a");
list.add("aa");
list.add("aaa");
list.add("aaaa");
list.forEach(new Consumer<String>(){//Consumer是一个接口,并且是函数式接口!
    @Override
    //这里的s就依次表示集合中的每个数据
    public void accept(String s){
        System.out.println(s);//这里的方法体就代表我们要对集合进行的操作
    }
});
//写Lambda表达式的时候如果不太熟可以先写匿名内部类,然后删减多余部分即可!
list.forEach(s->System.out.println(s));//简化写法

forEatch方法的底层:遍历集合依次得到每一个元素,每得到一个集合中的元素就会把数据传递给我们自己写的accept方法

四,List系列的集合

介绍:

  • List系列的集合是Collection单列集合中的一种,故Collection系列的所有方法List系列都可以使用
  • List集合因为有索引,所有多了很多对索引操作的特有的方法!
方法名 说明
void add(int index,E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引的元素,返回被删除的元素
E set(int index,E element) 修改指定索引的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素

细节:

  1. add方法:添加后,原来索引上的元素会依次往后移!

  2. remove方法:存在删除索引和删除指定元素的方法一个是重载Collection的,一个是Collection的方法

    list.remove(1)此时是删除1索引上的元素!而不是元素1

    理由:在调用方法的时候,如果方法出现了重载现象,优先调用实参和形参一致的那个方法!

    解决方法:手动装箱

java 复制代码
public class Main {
    public static void main(String[] args){
        List<Integer> list=new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.remove(1);//删除的是1索引内容
        System.out.println(list);
        Integer i=Integer.valueOf(1);//手动装箱
        list.remove(i);//删除的是元素1
        System.out.println(list);
    }
}

4.1 List集合的五种遍历方式

List是继承于Collection接口,故List也存在Collection的三种遍历方式,又List存在索引,故List也有自己特有的两种遍历方式

  1. 迭代器遍历
  2. 列表迭代器遍历
  3. 增强for遍历
  4. Lambda表达式遍历
  5. 普通for循环(因为List存在索引)

五种遍历方式的举例:

java 复制代码
public class Main {
    public static void main(String[] args){
        List<Integer> list=new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        //1.迭代器
        Iterator<String> it=list.iterator();
        while(it.hasNext()){
            String str=it.next();
            System.out.println(str);
        }
        //2.增强for
      	for(String s:list){
            System.out.println(s);
        }
        //3.Lambda表达式
        list.forEach(s->System.out.println(s));
        //4.普通for
        for(int i=0;i<list.size();i++){
            String s=list.get(i);
            System.out.println(s);
        }
    }
}

列表迭代器:是迭代器的子接口(迭代器的方法它都可以使用)

  • 获取列表迭代器

    java 复制代码
    List<String> arr=new ArrayList<>();
    ListIterator<Stirng> it=arr.listIterator();
  • 常用方法:

方法名 说明
boolean hasNext() 判断当前位置是否有元素,有元素返回true,没有返回false
E next() 获取当前位置的元素,并且迭代器对象移动到下一个位置
void remove() 删除当前迭代器指向的元素
void set(E e) 修改当前迭代器指向的元素
void add(E e) 给指针指向的元素添加元素在下一个位置上!
java 复制代码
public class Main {
    public static void main(String[] args){
        List<String> list=new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        ListIterator<String> it = list.listIterator();
        while(it.hasNext()){
            String i=it.next();
            if("bbb".equals(i)){
                it.add("qqq");
            }
        }
        System.out.println(list);
    }
}

总结:

  • 迭代器遍历 :在遍历的过程中需要删除索引,使用迭代器遍历
  • 列表迭代器 :在遍历的过程中需要添加元素,使用列表迭代器
  • 增强for遍历,Lambda表达式:只是想遍历,就用这个
  • 普通for :如果遍历的时候需要操作索引,使用普通for遍历

4.2 ArrayList集合

4.2.1 介绍

定义ArrayListList 接口的动态数组实现,可以存储任意类型的对象(通过泛型指定)。

核心特性

  • 元素有序(按插入顺序排列)
  • 元素可重复
  • 动态扩容(自动调整数组大小)
  • 非线程安全(多线程环境下需同步处理)

创建方法

java 复制代码
ArrayList<String> list=new ArrayListM<>();

常用方法:ArrayList的常用方法就是Collection的常用方法+List的常用方法

java 复制代码
ArrayList<String> list1=new ArrayListM<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
ArrayList<String> list2=new ArrayListM<>();
list2.addAll(list1);//直接将list1集合的全部内容添加到list2中

4.2.2 底层实现原理

数据结构

  • 基于 Object 数组 实现(源码中的 transient Object[] elementData

  • 默认初始容量为 10DEFAULT_CAPACITY = 10

  • 当数组容量不足时,自动触发扩容机制

扩容机制

  • 扩容公式 :新容量 = 旧容量 * 1.5(源码中的 int newCapacity = oldCapacity + (oldCapacity >> 1)

  • 扩容步骤

    1. 当添加元素时发现数组已满
    2. 创建一个新数组(大小为原数组的 1.5 倍)
    3. 将旧数组元素复制到新数组
    4. 新元素添加到新数组末尾

性能特点

  • 查询快:通过索引直接访问数组元素(时间复杂度 O(1))

  • 增删慢

    • 尾部插入快(O(1))
    • 中间插入/删除需移动元素(O(n))

4.2.3 使用场景

场景 说明
频繁查询 适合使用索引快速访问元素(如数据展示)
尾部增删 尾部操作效率高(如日志记录)
元素有序存储 需要保持插入顺序时使用
不涉及线程安全 单线程环境下优先选择(相比 Vector 性能更好)

4.2.4 常见问题与注意事项

  1. 并发修改异常

    • 问题 :在遍历集合时修改元素会抛出 ConcurrentModificationException

      java 复制代码
      ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
      for (String s : list) {
          if (s.equals("B")) {
              list.remove(s); // 抛出异常!
          }
      }
    • 解决方案

      • 使用迭代器的 remove() 方法
      • 使用 CopyOnWriteArrayList(并发场景)
  2. 优化建议

    • 预分配容量:如果已知数据量大小,初始化时指定容量避免频繁扩容
    • 避免中间插入 :大量中间插入操作应改用 LinkedList
  3. 与数组的转换

    • ArrayList → Array

      java 复制代码
      String[] arr = list.toArray(new String[0]);
    • Array → ArrayList

      java 复制代码
      ArrayList<String> list = new ArrayList<>(Arrays.asList(arr));
  4. 线程安全问题

    • 默认非线程安全:多线程环境下需同步处理

    • 解决方案

      java 复制代码
      List<String> syncList = Collections.synchronizedList(new ArrayList<>());

4.3 LinkedList集合

4.3.1 介绍

定义LinkedListList 接口的双向链表实现 ,同时实现了 Deque 接口,可用作队列或双端队列。

核心特性

  • 元素有序(按插入顺序排列)
  • 元素可重复
  • 无需扩容(动态添加节点)
  • 非线程安全(多线程环境下需同步处理)

LinkedList创建方法

java 复制代码
LinkedList<String> list=new LinkedList<>();

LinkedList独有的方法

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

4.3.2 底层实现原理

数据结构

  • 基于 双向链表 实现(源码中的 Node<E> 内部类)
  • 每个节点包含:
    • E item:存储元素
    • Node<E> next:指向下一个节点
    • Node<E> prev:指向前一个节点
  • 维护 firstlast 指针,分别指向链表的头和尾

操作原理

  • 插入/删除:只需修改相邻节点的引用(时间复杂度 O(1))
  • 随机访问:需要从头或尾遍历链表(时间复杂度 O(n))

性能特点

  • 增删快:头尾操作效率极高(如实现栈、队列时)
  • 查询慢:无法直接通过索引定位元素

4.3.3 使用场景

场景 说明
频繁头尾操作 实现栈、队列、双端队列的理想选择
中间插入/删除 相比 ArrayList 更高效(无需移动元素)
不确定数据量 无需担心扩容问题

4.3.4 常见问题与注意事项

  1. 线程安全问题
markdown 复制代码
 -   **默认非线程安全**:多线程操作需手动同步
go 复制代码
 -   **解决方案**:

     ```java
     List<String> syncList = Collections.synchronizedList(new LinkedList<>());
     ```
  1. 内存开销
bash 复制代码
 每个节点需要存储两个引用(prev/next),内存占用比 ArrayList 高约 3 倍
  1. 遍历性能
markdown 复制代码
 -   **避免通过索引遍历**(如 `list.get(i)`),时间复杂度为 O(n^2)
 -   **推荐使用迭代器**,时间复杂度为 O(n)

五,Set系列集合

介绍

  • Set系列集合添加的元素是无序不重复无索引

    无序:存和取得顺序不一致,即:存入1,2,3可能取出2,3,1

    不存放:集合内元素不重复

    无索引:没有带索引的方法,不能用普通for循环遍历,无法使用索引获取元素

  • Set集合的实现类:

    HashSet:无序,不重复,无索引

    LinkedHashSet:有序,不重复,无索引

    TreeSet:可排序,不重复,无索引

  • Set接口是基础Collection接口的,故基本方法和Collection一致

  • Set集合可以用Collection的三种遍历方式进行遍历

  • Set集合的add方法如果第二次添加相同的元素 结果会返回false

在 Java 中,Set 集合不提供直接的方法来修改元素的内容。Set 是一种不允许重复元素的集合,它的设计目的是用于存储和管理不重复的对象。因此,如果你想修改 Set 集合中的元素内容,你需要先删除旧的元素,然后再添加新的元素。

5.1 HashSet

HashSet的特点:无序不可重复无索引,允许一个null。

HashSet底层采用哈希表存储数据,哈希表是一种对于增删改查数据性能都比较好的数据结构

HashSet底层:

  • JDK8之前:数组+链表
  • JDK8开始:数组+链表+红黑树

数据存储是无序的:数据存入位置公式int index=(数组长度-1) & 哈希值

哈希值:

  • 根据hashCode方法计算出的int类型的整数
  • 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
  • 一般情况下会重写hashCode方法,利用对象内部的属性值计算哈希值

哈希值特点

  • 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
  • 如果重写了hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
  • 在小部分情况下,不同属性值或不同地址值计算出的哈希值也可能一样(哈希碰撞)

HashSet添加元素的情况

  • 扩容时机 :当数组内元素个数为:当前数组长度*默认加载因子16*0.75=12的时候扩容为原数组的两倍

  • 当链表长度>8且数组长度>64 ,链表就会转化为红黑树

  • 注意点:如果HashSet存储的自定义对象,必须重写hashCode和equals方法!

    重写hashCode目的是想根据属性值去计算哈希值

    重写equals目的是想比较对象内部属性

    Object类中的hashCode和equals都是通过地址值进行计算和比较的一般不用

  • HashSet存取顺序不一致:因为是根据哈希值存入的,通过数组0索引开始查,每个数据的哈希值不一致,不可能按照顺序存入

  • HashSet为何不用索引:因为哈希表用上了链表和红黑树,无法定义同一个数组下哪一个链表或红黑树节点为0索引,故不用

  • HashSet利用什么机制去重的:利用hashCode得到哈希值从而确定当前元素在数组中的位置,利用equals比较对象内部的属性值是否相同。这就是为何自定义对象的时候需要重写方法

  • 线程安全问题

    • 非线程安全:多线程操作可能导致数据不一致

    • 解决方案

      java 复制代码
      Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
      
      Set<String> safeSet = ConcurrentHashMap.newKeySet();

5.2 LinkedHashSet

介绍

  • LinkedHashSet是HashSet的子类,具有全部的HashSet的方法,不需要多记其API只需记住特性即可
  • 特性:有序,不重复,无索引,允许一个null
  • 性能平衡
    • 查找效率接近 HashSet(平均 O(1))
    • 插入/删除效率略低于 HashSet(需维护链表)
  • 允许 null 值 :可存储一个 null 元素
  • 非线程安全:多线程环境下需同步处理
  • LinkedHashSet会保证数据的存入和取出的顺序一致!
    • 原理 :底层数据结构依旧是哈希表,只是每个元素又额外多了一个双链表机制记录存储的顺序

8索引添加的是第一个元素,0索引是添加的最后一个元素,两个单独的指针代表头节点和尾节点

当要求数据去重且存取有序的时候才使用LinkedHashSet。要不然默认使用HashSet,因为LinkedHashSet效率较低

5.3 TreeSet

介绍

  • TreeSet是Set接口的子类,具有全部的Set接口的方法,不需要多记其API只需记住特性即可

  • 特性:

    • 元素唯一性 :不允许重复元素(依赖 compareTo()compare() 判断重复)
      • compareTo()compare() 返回 0 时,视为重复元素,拒绝插入
      • 不允许 NULL因为(compareTo()compare() 无法处理 null
    • 有序性 :元素自动排序(默认升序,可通过 Comparator 自定义)
    • 无索引:不能通过索引访问元素
    • 非线程安全:多线程环境下需手动同步
  • TreeSet底层是基于红黑树的数据结构实现排序的,增删改查的性能都比较好

    因为TreeSet底层是红黑树,故不需要重写equals方法和hashCode方法在自定义类中,但是要指定排序规则

  • TreeSet集合默认的规则

    1. 对于数值类型,Integer,Double,默认按照从小到大排序

    2. 对于字符,按照字符在ASCII码表中的数字升序排序

    3. 对于字符串:按照字典序

    4. 对于自定义的类型,需要手动添加排序规则!

    TreeSet的两种比较方式

    1. 默认排序/自然排序 :javabean类实现Comparable接口指定比较规则

      java 复制代码
      public class Student implements Comparable<Student>{//实现接口
          int age;
          String name;
          @Override//重写接口中的方法
          public int compareTo(Student o) {
              //指定排序规则
              //只看年龄,按照年龄的升序排序
              return this.getAge()-o.getAge();
              //this表示当前要添加的元素,o表示在已经在红黑树中存在的元素
              //返回值:
              //1.负数:认为要添加的元素是小的,存左边
              //2.正数:认为要添加的元素是大的,存右边
              //3.0:认为要添加的元素已经存在,舍弃
          }
      }
    2. 比较器排序 :创建TreeSet对象的时候,传递比较器Comparator指定规则

      使用原则:默认使用第一种,如果第一种无法满足需求才使用第二种

      例:存入四个字符串"c","ab","df","qwer",按照长度排序,长度一致按照首字母排序

      java 复制代码
      import java.util.*;
      public class Main {
          public static void main(String[] args) {
              TreeSet<String> ts=new TreeSet<>(new Comparator<String>() {
                  @Override
                  //o1当前要添加的元素,o2已经在红黑树存在的元素
                  //返回规则一样
                  public int compare(String o1, String o2) {
                      int i=o1.length()-o2.length();
                      i= i== 0 ? o1.compareTo(o2):i;//比较长度,如果长度一致使用默认排序规则compareTo,否则以长度为准
                      return i;
                  }
              });
              //这个Comparator接口是函数式接口,可以用Lambda表达式简化
              TreeSet<String> ts=new TreeSet<>((o1,o2)->{
                  int i=o1.length()-o2.length();
                  i= i== 0 ? o1.compareTo(o2):i;
                  return i;
              });
              ts.add("c");
              ts.add("ab");
              ts.add("df");
              ts.add("qwer");
              System.out.println(ts);
          }
      }

      如果两种方式都存在,方式二优先使用

六,Map

6.1 Map简述

双列集合特点:

  1. 双列集合一次需要存一对数据,分为键和值

  2. 键不能重复,值可以重复

  3. 键和值是一一对应的,每一个键只能找到自己对于的值

  4. 键+值这个整体,叫键值对 或键值对对象,在Java中叫Entry对象

  5. Map集合体系如下图:

Map系列集合常用API

  1. Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的

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

    在添加数据的时候,如果键不存在 ,那么直接把键值对对象添加到map集合中,返回null

    在添加数据的时候,如果键是存在的 ,那么会把原有的数据覆盖 ,并把被覆盖的值进行返回

6.2 Map系列集合遍历方式

6.2.1 键找值方式遍历

java 复制代码
import java.util.*;
public class Main {
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<>();
        map.put("a","A");
        map.put("b","B");
        map.put("c","C");
        map.put("d","D");
        //获取所有的键,把键放入单列集合中
        Set<String> keys = map.keySet();//keySet方法就是获取所有的key并返回Set集合
        
        for (String key : keys) {//Set集合可以使用的三种遍历方式,Lambda,增强for,迭代器iterator
            String value = map.get(key);//Map系列集合的get方法,通过key获取value
            System.out.println(key+"="+value);
        }
        
        //可以简化,不需要keys这个变量
        for (String key : map.keySet()) {
            String value = map.get(key);
            System.out.println(key+"="+value);
        }
        
    }
}

6.2.2 键值对方式遍历

java 复制代码
import java.util.*;
public class Main {
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<>();
        map.put("a","A");
        map.put("b","B");
        map.put("c","C");
        map.put("d","D");
        //通过entrySet方法获取键值对对象
        //Entry是Map接口中的内部接口
        //可以通过外部接口.内部接口方式使用内部接口,也可也通过import java.util.Map.Entry方式导入
        //这样就直接Entry<K,V>即可
        Set<Map.Entry<String, String>> entries = map.entrySet();
        //通过entries这个单列集合获取键值对对象内部的k和v
        //因为是set集合,可以使用set集合的三种遍历方式,这里暂时采用Lambda方式,因为最简单
        entries.forEach(s-> System.out.println(s.getKey()+"="+s.getValue()));
        //getKey是键值对对象的方法,获取key
        //getValue是键值对对象的方法,获取value
    }
}

6.2.3 Lambda表达式方式遍历

使用Map集合中下列方法遍历元素

方法名 说明
default void forEach(BiConsumer<? suoer K,? super V> action) 结合Lambda表达式遍历Map集合
java 复制代码
import java.math.BigDecimal;
import java.util.*;
import java.util.function.BiConsumer;

public class Main {
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<>();
        map.put("a","A");
        map.put("b","B");
        map.put("c","C");
        map.put("d","D");
        //这里写内部类形式方便理解
        map.forEach(new BiConsumer<String, String>() {
            @Override
            public void accept(String key, String value) {
                System.out.println(key+"="+value);
            }
        });
        //简化为Lambda表达式
         map.forEach((key,value)->System.out.println(key+"="+value));
    }
}

forEatch方法的底层:就是用第二种方式进行遍历,依次得到每一个键和值,再调用accept方法

6.1 HashMap

  • HashMap特点:

    1. HashMap是Map里面的一个实现类

    2. 没有额外需要学习的特有方法,直接使用Map中的方法即可

    3. 特点都是由键决定的:无序,不重复,无索引(这里都是关于键的特性,值无要求)

    4. HashMap和HashSet底层原理是一模一样的,都是采用哈希表结构

      存入的方式和HashSet也一样,只不过HashMap是根据键进判断的

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

    6. 如果值存储的是自定义对象,不需要重写

    7. 依赖hashCode和equals方法保证键的唯一

6.2 LinkedHashMap

  • LinkedHashMap特点:
    1. 由键绝对:有序,不重复,无索引。
    2. 原理:和LinkedHashSet一致。。。
    3. 方法和Map方法一样,不需要额外记。。

6.3 TreeMap

  • TreeMap和TreeSet一样都是红黑树结构

  • 由键决定:可排序,不重复,无索引

    可排序:对键进行排序

  • 这玩意和TreeSet唯一的区别是多了个Value其他用法和TreeSet一致,只需看TreeSet即可

  • 默认按照键的从小到大排序,也可自定义排序规则

    排序规则实现方式和TreeSet一样

    1. 在自定义类中实现Comparable接口,指定比较规则

    2. 创建集合的时候传递Comparator比较器对象,指定比较规则

七,可变参数

java 复制代码
//计算n个数据的和
//jdk5之前写法
int[] arr={1,2,3,4,5,6,7,8,9,10};
int sum=getSum(arr);
int get Sum(int[] arr){
    int sum=0;
    for(int i:arr){
        sum=sum+i
    }
    return i;
}
//jdk5开始的写法:
//可变参数:方法的形参的个数是可变的
//格式:数据类型...形参名
//int...nums
//举例
int sum=getSum(1,2,3,4,5,6,7,8,9,10);
int get Sum(int...nums){
    int sum=0;
    for(int i:nums){
        sum=sum+i
    }
    return i;
}
//底层:可变参数就是一个数组,是Java帮助我们创建好的数组,只需把nums当作数组调用即可

可变参数的小细节:

  1. 在方法的形参中,最多只能定义一个可变参数

    java 复制代码
    int get Sum(int...nums,int...a){//错误写法,无法判断前面参数应该接收多少个参数
    }
  2. 在方法当中,如果除了可变参数以外的形参数,那么可变参数必须在最后!

    java 复制代码
    int get Sum(int...nums,int a){//错误写法,无法判断前面参数应该接收多少个参数
    }
    int get Sum(int nums,int...a){//正确写法
    }

八,Collections类

  • Collections是java.util.Collections的类,是集合的工具类

  • 常用方法:

    方法名 说明
    public static boolean addAll(Collection c,T...elements) 批量添加元素
    public static void shuffle(List<?> list) 打乱List集合元素的顺序
    public static void sort(List list) 排序
    public static void sort(List,Comparator c) 根据指定规则排序
    public static int binarySearch(List list,T key) 以二分查找算法查找元素(元素必须有序
    public static int fill(List list,T obj) 使用指定元素填充集合
    public static void max/min(Collection coll) 根据默认的自然排序获取最大/最小值
    public static void swap(List<?> list,int i,int j) 交换指定索引的元素
  • 小细节:

    假设list集合里面为空,啥也没有,用fill里面是啥也填充不到的,因为不知道要填充多少。

    即要填充的集合里面必须有值才能填充(即将里面值全部替换成填充值)

    例题:

    java 复制代码
    //实现概率抽取集合元素
    //随机抽取集合中元素且,男占70%女占30%
    
    import java.lang.reflect.Array;
    import java.util.*;
    
    public class Main {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            Collections.addAll(list, 1, 1, 1, 1, 1, 1, 1);//1代表男生占比,6个即百分之60
            Collections.addAll(list, 0, 0, 0, 0);//0代表女生占比,4代表百分之40
            Collections.shuffle(list);//打乱
            Random r = new Random();
            int index = r.nextInt(list.size());//这样获取到男生占比60%女生占比40%
            int nums = list.get(index);
            System.out.println(nums);
    
            ArrayList<String> boy = new ArrayList<>();
            ArrayList<String> girl = new ArrayList<>();
            Collections.addAll(boy, "男1", "男2", "男3", "男4", "男5", "男6");
            Collections.addAll(girl, "女1", "女2", "女3", "女4");
            if (nums == 1) {
                int boyIndex = r.nextInt(boy.size());
                System.out.println(boy.get(boyIndex));
            } else if (nums == 0) {
                int girIndex = r.nextInt(girl.size());
                System.out.println(girl.get(girIndex));
            }
        }
    } 

九,不可变集合

使用场景:

  • 如果某个数据不能被修改,就放入到不可变集合中
  • 当集合对象被不信任的库调用的时候,不可变形式是安全的。

简单理解:不想让外界修改集合中的内容

格式

java 复制代码
//1.创建不可变的List集合
List<T> list=List.of(Element...元素);
//2.创建不可变的Set集合
Set<T> set=Set.of(Element...元素);
//3.创建不可变的Map集合
Map<K,V> map=Map.of(k1,v1,k2,v2,k3,v3....);

细节:

  1. 不可变集合一旦创建就无法修改,只能查询

  2. Set类型的不可变集合里面的元素不能重复!

  3. Map类型的集合默认把第一个参数当作K,第二个参数当作V以此类推

  4. Map集合的键不能重复

  5. Map里面的of方法有上限,最多传递20个参数。

    因为其他两个是用可变参数传递的

    Map集合是通过方法重载的方式写了一堆of方法

    解决方法:传递Entry键值对对象即可

    java 复制代码
    //创建一个普通map集合
    HashMap<String,String> hm=new HashMap<>();
    hm.put("a","A");
    hm.put("b","B");
    hm.put("c","C");
    hm.put("d","D");
    //2.利用上面的数据获取一个不可变集合
    //获取entry对象
    Set<Map.Entry<String,String>> entries=hm.entrySet();
    //把entries变成数组
    //使用方法 toArray(指定类型)
    Map.Entry[] arr = entries.toArray(new Map.Entry[0]);
    //这里的new Map.Entry[0]就是创建一个Map.Entry数组,长度为0
    //toArray底层细节:toArray底层会比较集合长度和数组长度大小,
    //如果集合长度>数组长度,即数据在数组中放不下,此时会根据实际数据个数重新创建数组
    //如果集合长度<=数组长度,即数据在数组中放得下,直接使用这个数组
    Map map=Map.ofEntries(arr);//将键值对对象传入不可变Map集合
    
    //上述方法太难记了可使用Map集合的copyOf方法构建一个不可变map集合(jdk10才有这个方法)
    Map map=Map.copyOf(hm);//这个底层就是上述代码
相关推荐
橘猫云计算机设计2 分钟前
基于springboot的考研成绩查询系统(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·python·考研·django·毕业设计
有一只柴犬16 分钟前
深入Spring AI:6大核心概念带你入门AI开发
spring boot·后端
Aurora_NeAr23 分钟前
深入理解Java虚拟机-垃圾收集器与内存分配策略
后端
向阳25632 分钟前
SpringBoot+vue前后端分离整合sa-token(无cookie登录态 & 详细的登录流程)
java·vue.js·spring boot·后端·sa-token·springboot·登录流程
你的人类朋友1 小时前
JS严格模式,启动!
javascript·后端·node.js
Aurora_NeAr1 小时前
深入理解Java虚拟机-Java内存区域与内存溢出异常
后端
风象南1 小时前
SpringBoot实现数据库读写分离的3种方案
java·spring boot·后端
lzj20141 小时前
DataPermissionInterceptor源码解读
后端
ChinaRainbowSea1 小时前
3. RabbitMQ 的(Hello World) 和 RabbitMQ 的(Work Queues)工作队列
java·分布式·后端·rabbitmq·ruby·java-rabbitmq
dleei1 小时前
MySql安装及SQL语句
数据库·后端·mysql