Java集合

集合的体系结构

可分为:Collection(单列集合)和 Map(双列集合)。

屏幕截图 2026-03-21 133609

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

Set 系类集合:添加的元素是无序、不重复、无索引

Collection

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

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

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo {
    public static void main(String[] args) {
        /*
            public boolean add(E e)                 把给定的对象添加到当前集合中
            public void clear()                     清空集合中所有的元素
            public boolean remove(E e)              把给定的对象在集合中删除
            public boolean contains(Object obj)     判断当前集合中是否包含给定的对象
            public boolean isEmpty()                判断当前集合是否为空
            public int size()                       返回集合中元素的个数/集合的长度

            注意点:
            Collection 是一个接口,我们不能直接创建它的对象
            所以,现在我们学习他的方法时,只能创建他实现类的对象
            实现类:ArrayList
         */

        // 目的:为了学习 Collection 接口里面的方法
        // 自己在做一些练习的时候,还是按照之前的方式去创建对象
        Collection<String> coll = new ArrayList<>();

        // 1. 添加元素
        // 细节1:如果我们要往 List 系列集合中添加数据,那么方法永远返回 true,因为 List 系列的是允许元素重复的
        // 细节2:如果我们要往 Set 系列集合中添加数据,如果当前要添加的元素不存在,方法返回 true,表示添加成功;
        //                                     如果当前要添加的元素已经存在,方法返回 false,表示添加失败
        //                                     因为 Set 系列的集合不允许重复
        coll.add("aaa");
        coll.add("bbb");
        coll.add("ccc");
        System.out.println(coll);

        // 2. 清空
//        coll.clear();
//        System.out.println(coll);

        // 3. 删除
        // 细节1:因为 Collection 里面定义的是共性的方法,所以此时不能通过索引进行删除,只能通过元素的对象进行删除
        // 细节2:方法会有一个布尔类型的返回值,删除成功返回 true,删除失败返回 false
        // 如果要删除的元素不存在,就会失败
        coll.remove("aaa");
        System.out.println(coll);

        // 4. 判断集合中是否包含某个元素
        // 细节:底层是依赖 equals 方法进行判断是否存在的
        // 所以,如果集合中存储的是自定义对象,也想通过 contains 方法判断元素是否存在,那么在 Javabean 类中一定要重写 equals 方法
        System.out.println(coll.contains("aaa"));
        System.out.println(coll.contains("bbb"));

        // 5. 判断集合是否为空
        System.out.println(coll.isEmpty());

        // 6. 获取集合的长度
        System.out.println(coll.size());
        coll.add("ddd");
        System.out.println(coll.size());
    }
}
package com.faxont.collection;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo2 {
    public static void main(String[] args) {
        // 1. 创建集合的对象
        Collection<Student> coll = new ArrayList<>();

        // 2. 创建三个学生对象
        Student s1 = new Student("zhangsan", 23);
        Student s2 = new Student("lisi", 24);
        Student s3 = new Student("wangwu", 25);

        // 3. 把学生对象添加到集合当中
        coll.add(s1);
        coll.add(s2);
        coll.add(s3);

        // 4. 判断集合中某个学生对象是否包含
        Student s4 = new Student("zhangsan", 23);
        // 如果姓名年龄相同,就认为是同一个学生
        // 因为 Contains 方法在底层是调用 equals 方法判断对象是否一致的
        // 所以,如果 equals 方法没有被重写,那么 Contains 方法判断对象是否一致,就是比较地址值
        System.out.println(coll.contains(s4));// true
    }
}
package com.faxont.collection;

import java.util.Objects;

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Collection 的遍历方式

迭代器遍历

迭代器不依赖索引

Java 中迭代器的核心是 java.util.Iterator 接口,迭代器是遍历集合(如 ArrayList、HashSet 等)元素的标准工具 / 方式。

Collection 集合获取迭代器

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

Iterator 中的常用方法

方法名称 说明
boolean hasNext() 判断当前位置是否有元素,有元素返回 true,没有元素返回 false
E next() 获取当前位置的元素,并将迭代器对象移向下一个位置
package com.faxont.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionDemo3 {
    public static void main(String[] args) {
        /*
            Collection 系列集合三种通用的遍历方式:
                1. 迭代器遍历
                2. 增强 for 遍历
                3. lambda 表达式遍历

                迭代器遍历相关的三个方法:
                        Iterator<E> iterator() : 获取一个迭代器对象
                        boolean hasNext()      : 判断当前指向的位置是否有元素
                        E next()               : 获取当前指向的元素并移动指针
         */

        // 1. 创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("aaa");
        coll.add("bbb");
        coll.add("ccc");
        coll.add("ddd");

        // 2, 获取迭代器对象
        // 迭代器就好比是一个箭头,默认指向集合的 0 索引处
        Iterator<String> it = coll.iterator();
        // 3. 利用循环不断地去获取集合中的每一个元素
        while (it.hasNext()) {
            // 4. next 方法的两件事情:获取元素并移动指针
            String s = it.next();
            System.out.println(s);
        }
    }
}

注意:

  1. 当指针指向末尾无元素处时,仍然调用 next() 方法获取元素,会报错 NoSuchElementException
  2. 迭代器遍历完毕,指针不会复位
  3. 循环中只能用一次 next 方法
  4. 迭代器遍历时,不能用集合的方法进行增加或者删除
package com.faxont.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionDemo4 {
    public static void main(String[] args) {
        /*
            1. 当指针指向末尾无元素处时,仍然调用 next() 方法获取元素,会报错 NoSuchElementException
            2. 迭代器遍历完毕,指针不会复位
            3. 循环中只能用一次 next 方法
            4. 迭代器遍历时,不能用集合的方法进行增加或者删除
         */

        // 1. 创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("aaa");
        coll.add("bbb");
        coll.add("ccc");
        coll.add("ddd");

        // 2. 获取迭代器对象
        // 迭代器就好比是一个箭头,默认指向集合的 0 索引处
        Iterator<String> it = coll.iterator();
        // 3. 利用循环不断地去获取集合中的每一个元素
        while(it.hasNext()){
            // 4. next 方法的两件事情:获取元素并移动指针
            String s = it.next();
            System.out.println(s);
        }

        // 当上面的循环结束之后,迭代器的指针已经指向了最后没有元素的位置
//        System.out.println(it.next());

        // 迭代器遍历完毕,指针不会复位
//        System.out.println(it.hasNext());// false
//        System.out.println(it.hasNext());// false

        // 如果我们要继续第二次遍历集合,只能再次获取一个新的迭代对象
        System.out.println("------------------");
        Iterator<String> it2 = coll.iterator();
        while(it2.hasNext()){
            String s = it2.next();
            System.out.println(s);
        }
    }
}
package com.faxont.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionDemo5 {
    public static void main(String[] args) {
        /*
            1. 当指针指向末尾无元素处时,仍然调用 next() 方法获取元素,会报错 NoSuchElementException
            2. 迭代器遍历完毕,指针不会复位
            3. 循环中只能用一次 next 方法
            4. 迭代器遍历时,不能用集合的方法进行增加或者删除
                    如果实在需要删除,可以使用迭代器提供的 remove() 方法进行删除
         */

        // 1. 创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("aaa");
        coll.add("bbb");
        coll.add("ccc");
        coll.add("ddd");
        coll.add("eee");

        // 2. 获取迭代器对象
        // 迭代器就好比是一个箭头,默认指向集合的 0 索引处
        Iterator<String> it = coll.iterator();
        // 3. 利用循环不断地去获取集合中的每一个元素
        while(it.hasNext()){
            // 4. next 方法的两件事情:获取元素并移动指针
//            System.out.println(it.next());// aaa ddd eee
//            System.out.println(it.next()); // bbb ccc (NoSuchElementException)

            String s = it.next();
            if("bbb".equals(s)){
//                coll.remove("bbb");// ConcurrentModificationException
                it.remove();
            }
        }
        System.out.println(coll);


    }
}

增强 for 循环遍历

  • 增强 for 的底层就是迭代器,为了简化迭代器的代码书写的。
  • 它是 JDK5 之后出现的,其内部原理就是一个 Iterator 迭代器。
  • 所有的单列集合和数组才能用增强 for 进行遍历
// 格式
for(元素的数据类型 变量名 : 数组或者集合){
    
}
// 例子
for(String s : list){
    System.out,println(s);
}
package com.faxont.collection;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo6 {
    public static void main(String[] args) {
        /* Collection 系列集合三种通用的遍历方式:
            1. 迭代器遍历
            2. 增强 for 遍历
            3. lambda 表达式遍历

            增强 for 遍历格式:
            for(元素数据类型 变量名 : 集合/数组名){}
         */

        // 1. 创建集合
        Collection<String> coll = new ArrayList<>();
        coll.add("zhangsan");
        coll.add("lisi");
        coll.add("wangwu");

        // 2. 增强 for 遍历
        // 注意点:
        // s 其实就是一个第三方变量,在循环的过程中依次表示集合中的每一个数据
        for(String s : coll){
            s = "aaa";
        }
        System.out.println(coll);// [zhangsan, lisi, wangwu]
    }
}

增强 for 的细节

  • 修改增强 for 中的变量,不会改变集合中原本的数据。
for(String s : list){
	s = "a";
}
System.out.println(list);// 输出的还是原本的集合

Lambda 表达式遍历

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

方法名称 说明
defalut void forEach(Consumer<? super T> action): 结合 lambda 遍历集合
package com.faxont.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;

public class CollectionDemo7 {
    public static void main(String[] args) {
        /* Collection 系列集合三种通用的遍历方式:
            1. 迭代器遍历
            2. 增强 for 遍历
            3. lambda 表达式遍历

            lambda 表达式遍历:
                    default void forEach(Consumer<? super E> action):
         */

        // 1. 创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("zhangsan");
        coll.add("lisi");
        coll.add("wangwu");

        // 2. 利用匿名内部类的方式遍历
        // forEach 的底层原理:
        // 其实也会自己遍历集合,依次得到每一个元素
        // 把得到的每一个元素,传递给下面的 accept 方法
        // s 依次表示集合中的每一个元素
//        coll.forEach(new Consumer<String>() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        });

        // 3. 利用 lambda 表达式进行遍历
        coll.forEach(s -> System.out.println(s));
    }
}

总结

  1. Collection 是单列集合的顶层接口,所有方法被 List 和 Set 系列集合共享
  2. 常见成员方法:

​ add、clear、remove、contains、isEmpty、size

  1. 三种通用的遍历方式:
  • 迭代器:在遍历的过程中需要删除元素,请使用迭代器。
  • 增强for、Lambda:仅仅想遍历,则使用增强 for 或 Lambda 表达式。

List 集合

List 集合的特点

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

List 集合的特有方法

  • Collection 的方法 List 都继承了。
  • List 集合因为有索引,所以多了很多索引操作的方法。
方法名称 说明
void add(int index, E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index, E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素
package com.faxont.list;

import java.util.ArrayList;
import java.util.List;

public class ListDemo1 {
    public static void main(String[] args) {
        /*
            void add(int index, E element)      在此集合中的指定位置插入指定的元素
            E remove(int index)                 删除指定索引处的元素,返回被删除的元素
            E set(int index, E element)         修改指定索引处的元素,返回被修改的元素
            E get(int index)                    返回指定索引处的元素
         */

        // 1. 创建一个集合
        List<String> list = new ArrayList<>();

        // 2. 添加元素
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");

        // void add(int index, E element)      在此集合中的指定位置插入指定的元素
        // 细节:原来索引上的元素会依次往后挪
        list.add(1, "QQQ");

        // E remove(int index)                 删除指定索引处的元素,返回被删除的元素
        String remove = list.remove(0);
        System.out.println(remove);// aaa   返回被删除的元素

        // E set(int index, E element)         修改指定索引处的元素,返回被修改的元素
        String set = list.set(0, "aaa");
        System.out.println(set);// QQQ  返回被修改的元素

        // E get(int index)                    返回指定索引处的元素
        String get = list.get(0);
        System.out.println(get);

        // 3. 打印集合
        System.out.println(list);
    }
}
package com.faxont.list;

import java.util.ArrayList;
import java.util.List;

public class ListDemo2 {
    public static void main(String[] args) {
        // 1. 创建集合并添加元素
        List<Integer> list = new ArrayList<>();

        list.add(1);
        list.add(2);
        list.add(3);

        // 2. 删除元素
        // 请问:此时删除的是 1 这个元素,还是在 1 索引上的元素?
        // 为什么?
        // 因为在调用方法的时候,如果方法出现了重载现象
        // 优先调用,实参与形参类型一致的那个方法
//        list.remove(1);

        // 手动装箱:手动把基本数据类型的 1,变成 Integer 类型
        Integer i = Integer.valueOf(1);// Integer i = new Integer(1);
        list.remove(i);
        System.out.println(list);
    }
}

List 的遍历方式

  1. 迭代器遍历

  2. 列表迭代器遍历

  3. 增强 for遍历

  4. Lambda 表达式遍历

  5. 普通 for 循环遍历(因为 List 集合存在索引)

package com.faxont.list;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class ListDemo3 {
    public static void main(String[] args) {
        /*
            List 集合五种遍历方式:
            1. 迭代器遍历
            2. 列表迭代器遍历
            3. 增强 for遍历
            4. Lambda 表达式遍历
            5. 普通 for 循环遍历(因为 List 集合存在索引)
         */

        // 创建集合并添加元素
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");

        // 1. 迭代器遍历
//        Iterator<String> it = list.iterator();
//        while(it.hasNext()){
//            String s = it.next();
//            System.out.println(s);
//        }

        // 2. 增强 for 遍历
//        for (String s : list) {
//            System.out.println(s);
//        }

        // 3. Lambda 遍历
        // forEach 方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
        // 并把每一个元素传递给下面的 accept 方法
//        list.forEach(s -> System.out.println(s));

        // 4. 普通 for 循环遍历
//        for (int i = 0; i < list.size(); i++) {
//            System.out.println(list.get(i));
//        }

        // 5. 列表迭代器遍历
        // 增加了一个新方法 add 用来在迭代器中添加元素
        ListIterator<String> it = list.listIterator();
        while(it.hasNext()){
            String s = it.next();
            if("bbb".equals(s)){
                it.add("qqq");
            }
            System.out.println(s);
        }
        System.out.println(list);
    }
}

五种遍历方式对比

  • 迭代器遍历:在遍历的过程中需要删除元素,请使用迭代器
  • 列表迭代器:在遍历的过程中需要添加元素,请使用列表迭代器
  • 增强 for 循环 / Lambda 表达式:仅仅想遍历,那么使用增强 for 或 Lambda 表达式
  • 普通 for:如果遍历的时候想操作索引,可以用普通 for

数据结构概述

数据结构是计算机底层存储、组织数据的方式。

是指数据相互之间是以什么方式排列在一起的。

数据结构是为了更加方便地管理和使用数据,需要结合具体的业务场景来进行选择。一般情况下,精心选择的数据结构可以带来更高的运行或者存储效率。

  1. 每种数据结构长什么样子?
  2. 如何添加数据?
  3. 如何删除数据?

栈

栈的特点:后进先出,先进后出。

数据进入栈模型的过程称为:压 / 进栈。

数据离开栈模型的过程称为:弹 / 出栈。

队列

队列的特点:先进先出,后进后出。

数据从后端进入队列模型的过程称为:入队列。

数据从前端进入队列模型的过程称为:出队列。

数组

数组是一种查询快,增删慢的模型。

  • 查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)
  • 删除效率低:要将原始数据删除,同时后面每个数据前移。
  • 添加效率极低:添加位置后的每个数据后移,再添加元素。

链表

链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。

链表查询慢,无论查询哪个数据都要从头开始找。

链表增删相对快。

ArrayList

ArrayList 集合底层原理

  1. 利用空参创建的集合,在底层创建一个默认长度为0的数组
  2. 添加第一个元素时,底层会创建一个新的长度为10的数组
  3. 存满时,会扩容1.5倍
  4. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准

LinkedList

  • 底层数据结构是双链表,查询慢,增删快,但是如果朝左的是首尾元素,速度也是极快的。
  • LinkedList 本身多了很多直接操作首尾元素的特有 API。
特有方法 说明
public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素

泛型深入

泛型:是 JDK5 中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。

泛型的格式:<数据类型>

注意:泛型只能支持引用数据类型。

package com.faxont.generics;

import java.util.ArrayList;
import java.util.Iterator;

public class GenericsDemo1 {
    public static void main(String[] args) {
        // 没有泛型的时候,集合如何存储数据
        // 结论:
        // 如果我们没有给集合指定类型,默认认为所有的数据类型都是 Object 类型
        // 此时可以往集合添加任意的数据类型
        // 带来一个坏处,我们在获取数据的时候,无法使用它特有的行为

        // 此时推出了泛型,可以在添加数据的时候就把数据类型进行统一
        // 而且我们在获取数据的时候,也省得强转了,非常的方便

        // 1. 创建集合的对象
        ArrayList list = new ArrayList();

        // 2. 添加数据
        // add(Object obj)
        list.add("add");// String
        list.add(123);// Integer
        list.add(true);// Boolean
        list.add(new Student("zhangsan", 20));// 自定义类

        // 3. 遍历集合
        Iterator it = list.iterator();
        while(it.hasNext()){
            Object next = it.next();
            // 多态的弊端是无法访问子类的特有功能
            System.out.println(next);
        }
    }
}
package com.faxont.generics;

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}

泛型的好处

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

扩展知识点:Java 中的泛型是伪泛型

屏幕截图 2026-03-23 223807

泛型的细节

  • 泛型中不能写基本数据类型
  • 指定泛型的具体类型后,传递数据时,可以传入该类型或者其子类类型
  • 如果不写泛型,类型默认是 ObJect

泛型可以在很多地方进行定义

  • 类后面——泛型类
  • 方法上面——泛型方法
  • 接口后面——泛型接口

泛型类

使用场景:当一个类中,某个变量的数据类型不确定时,可以定义带有泛型的类

// 格式
修饰符 class 类名<类型> {
    
}

// 举例
public class ArrayList<E> {// 创建该类对象时,E 就确定类型
    
}

此处 E 可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成:T、E、K、V等

package com.faxont.generics;


import java.util.Arrays;

/**
 * 当我在编写一个类的时候,如果不能确定类型,那么这个类型就可以定义为泛型类
 */
public class MyArrayList<E> {

    Object[] obj = new Object[10];
    int size;

    /**
     * E:表示是不确定的类型,该类型在类名后面已经定义过了
     * @param e:形参的名字,变量名
     * @return
     */
    public boolean add(E e){
        obj[size++] = e;
        return true;
    }

    public E get(int index){
        return (E) obj[index];
    }

    @Override
    public String toString() {
        return Arrays.toString(obj);
    }
}
package com.faxont.generics;

public class GenericsDemo2 {
    public static void main(String[] args) {
        MyArrayList<String> list = new MyArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");

        System.out.println(list);
        System.out.println(list.add("ddd"));
        System.out.println(list.get(0));
        System.out.println(list);
    }
}

泛型方法

方法中形参类型不确定时,可以使用类名后面定义的泛型

// 格式
修饰符 <类型> 返回值类型 方法名(类型 变量名){
    
}

// 举例
public <T> void show(T t){// 调用该方法时,T 就确定类型
    
}

泛型方法的练习

定义一个工具类:ListUtil

类中定义一个静态方法 addAll,用来添加多个集合的元素。

package com.faxont.generics;

import java.util.ArrayList;

public class ListUtil {
    private ListUtil(){}
    public static <E> void addAll(ArrayList<E> list, E... array){
        for (E e : array) {
            list.add(e);
        }
    }
}
package com.faxont.generics;

import java.util.ArrayList;

public class GenericsDemo3 {
    public static void main(String[] args) {
        /*
            定义一个工具类:ListUtil
            类中定义一个静态方法 addAll,用来添加多个集合的元素。
         */
        ArrayList<String> list = new ArrayList<>();
        ListUtil.addAll(list, "aaa", "bbb", "ccc");
        System.out.println(list);
    }
}

泛型接口

// 格式
修饰符 interface 接口名<类型>{
    
}

// 举例
public interface List<E>{
    
}

重点:如何使用一个带泛型的接口

方式1:实现类给出具体类型 方式2:实现类延续泛型,创建对象时再确定

package com.faxont.generics;

public class GenericsDemo4 {
    public static void main(String[] args) {
        /*
            泛型接口的两种使用方式:
                1. 实现类给出具体的类型
                2. 实现类延续泛型,创建实现类对象时再确定类型
         */

        // 1. 实现类给出具体的类型
        MyArrayList2 list = new MyArrayList2();
        list.add("aaa");

        // 2. 实现类延续泛型,创建实现类对象时再确定类型
        MyArrayList3<String> list2 = new MyArrayList3<>();
        list2.add("bbb");
    }
}
package com.faxont.generics;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class MyArrayList2 implements List<String> {
    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public boolean contains(Object o) {
        return false;
    }

    @Override
    public Iterator<String> iterator() {
        return null;
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return null;
    }

    @Override
    public boolean add(String s) {
        return false;
    }

    @Override
    public boolean remove(Object o) {
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean addAll(Collection<? extends String> c) {
        return false;
    }

    @Override
    public boolean addAll(int index, Collection<? extends String> c) {
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return false;
    }

    @Override
    public void clear() {

    }

    @Override
    public String get(int index) {
        return null;
    }

    @Override
    public String set(int index, String element) {
        return null;
    }

    @Override
    public void add(int index, String element) {

    }

    @Override
    public String remove(int index) {
        return null;
    }

    @Override
    public int indexOf(Object o) {
        return 0;
    }

    @Override
    public int lastIndexOf(Object o) {
        return 0;
    }

    @Override
    public ListIterator<String> listIterator() {
        return null;
    }

    @Override
    public ListIterator<String> listIterator(int index) {
        return null;
    }

    @Override
    public List<String> subList(int fromIndex, int toIndex) {
        return null;
    }
}
package com.faxont.generics;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class MyArrayList3<E> implements List<E> {
    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public boolean contains(Object o) {
        return false;
    }

    @Override
    public Iterator<E> iterator() {
        return null;
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return null;
    }

    @Override
    public boolean add(E e) {
        return false;
    }

    @Override
    public boolean remove(Object o) {
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return false;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return false;
    }

    @Override
    public void clear() {

    }

    @Override
    public E get(int index) {
        return null;
    }

    @Override
    public E set(int index, E element) {
        return null;
    }

    @Override
    public void add(int index, E element) {

    }

    @Override
    public E remove(int index) {
        return null;
    }

    @Override
    public int indexOf(Object o) {
        return 0;
    }

    @Override
    public int lastIndexOf(Object o) {
        return 0;
    }

    @Override
    public ListIterator<E> listIterator() {
        return null;
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return null;
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        return null;
    }
}

泛型的继承和通配符

  • 泛型不具备继承性,但是数据具备继承性
package com.faxont.generics;

import java.util.ArrayList;

public class GenericsDemo5 {
    public static void main(String[] args) {
        /*
            泛型不具备继承性,但是数据具备继承性
         */

        // 创建集合的对象
        ArrayList<GrandParent> grandparents = new ArrayList<>();
        ArrayList<Parent> parents = new ArrayList<>();
        ArrayList<Child> children = new ArrayList<>();
        ArrayList<Student2> students = new ArrayList<>();

        // 调用 method() 方法
//        method(grandparents);
//        method(parents);// 错误
//        method(children);// 错误

        // 数据具备继承性
        grandparents.add(new GrandParent());
        grandparents.add(new Parent());
        grandparents.add(new Child());
        parents.add(new Parent());
        parents.add(new Child());
        children.add(new Child());

        method2(grandparents);
        method2(parents);
        method2(children);
        method2(students);

        method3(grandparents);
        method3(parents);
        method3(children);
//        method3(students);// 错误

        method4(grandparents);
        method4(parents);
        method4(children);
//        method4(students);// 错误
    }

    public static void method(ArrayList<GrandParent> list){}

    public static <E> void method2(ArrayList<E> list){

    }

    public static void method3(ArrayList<? extends GrandParent> list){

    }
    public static void method4(ArrayList<? super Child> list){

    }
}

class GrandParent{}
class Parent extends GrandParent{}
class Child extends Parent{}
class Student2{}
package com.faxont.generics;

import java.util.ArrayList;

public class GenericsDemo6 {
    public static void main(String[] args) {
        /**
         * 需求:
         *      定义一个方法,形参是一个集合,但是集合中的数据类型不确定
         */

    }

    /**
     * 此时,我们可以使用泛型的通配符:
     *      ? 也表示不确定的类型
     *      他可以进行类型的限定
     *      ? extends E 表示集合中的数据类型是E或者E的子类
     *      ? super E 表示集合中的数据类型是E或者E的父类
     *
     * 应用场景:
     *      1. 如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口
     *      2. 如果类型不确定,但是能知道以后只能传递某个继承体现中的数据,那么就可以使用泛型通配符
     * 泛型的通配符:
     *      关键点:可以限定类型的范围
     *      
     * @param list
     */
    public static void method(ArrayList<? extends GrandParent> list){

    }
    public static void method1(ArrayList<? super Child> list){

    }

    /**
     * 此时,泛型里面写的是什么类型,那么只能传递什么类型的数据
     * 弊端:
     *      利用泛型方法有一个小弊端,此时可以该接收任意的数据类型
     *      GrandParent Parent Child    Student
     * @param list
     * @param <E>
     */
    public static <E> void method2(ArrayList<E> list){

    }
}

小练习

package com.faxont.practice;

public abstract class Animal {

    public Animal(){};
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }

    private String name;
    private int age;

    public abstract void eat();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

abstract class Cat extends Animal {
}

class PersianCat extends Cat {

    @Override
    public void eat(){
        System.out.println("一只叫做" + getName() + "的" + getAge() + "岁的波斯猫,正在吃小饼干");
    }

}

class TabbyCat extends Cat {

    @Override
    public void eat(){
        System.out.println("一只叫做" + getName() + "的" + getAge() + "岁的狸花猫,正在吃鱼");
    }

}

abstract class Dog extends Animal {
}

class Teddy extends Dog {

    @Override
    public void eat(){
        System.out.println("一只叫做" + getName() + "的" + getAge() + "岁的泰迪,正在吃骨头,边吃边蹭");
    }
}

class Husky extends Dog {

    @Override
    public void eat(){
        System.out.println("一只叫做" + getName() + "的" + getAge() + "岁的哈士奇,正在吃骨头,边吃边拆家");
    }

}
package com.faxont.practice;

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        /*
            需求:
                定义一个继承结构
                                    动物
                        |                           |
                        猫                          狗
                    |       |                   |       |
                  波斯猫   狸花猫                泰迪     哈士奇

              属性:名字、年龄
              行为:吃东西
                    方法体打印:一只叫做...的,...岁的波斯猫,正在吃小饼干
                    方法体打印:一只叫做...的,...岁的狸花猫,正在吃鱼
                    方法体打印:一只叫做...的,...岁的泰迪,正在吃骨头,边吃边蹭
                    方法体打印:一只叫做...的,...岁的哈士奇,正在吃骨头,边吃边拆架

            测试类中定义一个方法用于饲养动物
                public static void keepPet(ArrayList<???> list){
                    // 遍历集合,调用动物的 eat 方法
                }

            要求1:该方法能养所有品种的猫,但是不能养狗
            要求2:该方法能养所有品种的狗,但是不能养猫
            要求3:该方法能养所有的动物,但是不能传递其它类型
         */

        ArrayList<PersianCat> persianCats = new ArrayList<>();
        ArrayList<TabbyCat> tabbyCats = new ArrayList<>();
        ArrayList<Teddy> teddys = new ArrayList<>();
        ArrayList<Husky> huskies = new ArrayList<>();

        keepPet(persianCats);
        keepPet(tabbyCats);

        keepPet2(teddys);
        keepPet2(huskies);

        keepPet3(persianCats);
        keepPet3(tabbyCats);
        keepPet3(teddys);
        keepPet3(huskies);
    }

    // 要求1:该方法能养所有品种的猫,但是不能养狗
    public static void keepPet(ArrayList<? extends Cat> list){
        // 遍历集合,调用动物的 eat 方法
        for(Cat cat : list){
            cat.eat();
        }
    }

    // 要求2:该方法能养所有品种的狗,但是不能养猫
    public static void keepPet2(ArrayList<? extends Dog> list){
        // 遍历集合,调用动物的 eat 方法
        for(Dog dog : list){
            dog.eat();
        }
    }

    // 要求3:该方法能养所有的动物,但是不能传递其它类型
    public static void keepPet3(ArrayList<? extends Animal> list){
        // 遍历集合,调用动物的 eat 方法
        for(Animal animal : list){
            animal.eat();
        }
    }

}

数据结构(树)

度:每一个节点的子节点数量 二叉树中,任意节点的度<=2

二叉查找树

二叉查找树,又称二叉排序树或者二叉搜索树

特点:

  • 每一个节点上最多有两个子节点
  • 任意节点左子树上的值都小于当前节点
  • 任意节点右子树上的值都大于当前节点

添加规则:小的放左边,大的放右边,一样的不存

二叉树的遍历方式

前序遍历

从根节点开始,然后按照当前节点,左子节点,右子节点的顺序遍历

IMG_20260324_183557

中序遍历

从最左边的子节点开始,然后按照左子节点,当前节点,右子节点的顺序遍历

IMG_20260324_183648

后序遍历

从最左边的子节点开始,然后按照左子节点,右子节点,当前节点的顺序遍历

IMG_20260324_183734

层序遍历

一层一层地去遍历

平衡二叉树

规则:任意节点左右子树高度差不超过1

平衡二叉树的旋转机制

问:平衡二叉树是怎么保持平衡的呢?

答:平衡二叉树的旋转机制

规则1:左旋 规则2:右旋 触发时机:当添加一个节点之后,该树不再是一颗平衡二叉树

左旋

确定支点:从添加的节点开始,不断地往父节点找不平衡的节点

情况一

屏幕截图 2026-03-24 191848

步骤:

  1. 以不平衡的点作为支点
  2. 把支点左旋降级,变成左子节点
  3. 晋升原来的右子节点
情况二

屏幕截图 2026-03-24 191919

步骤:

  1. 以不平衡的点作为支点
  2. 将根节点的右侧往左拉
  3. 原先的右子节点变成新的父节点,并把多余的左子节点出让给已经降级的根节点当右子节点

屏幕截图 2026-03-24 192305

右旋

跟左旋刚好相反

需要旋转的四种情况

左左

当根节点左子树的左子树有节点插入,导致二叉树不平衡,一次右旋即可

左右

当根节点左子树的右子树有节点插入,导致二叉树不平衡

先局部左旋,变为左左的情况,再整体右旋

右右

当根节点右子树的右子树有节点插入,导致二叉树不平衡,一次左旋即可

右左

当根节点右子树的左子树有节点插入,导致二叉树不平衡

先局部右旋,变为右右的情况,再整体左旋

红黑树

  • 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
  • 1972年出现,当时被称之为平衡二叉B树。后来,1978年被修改为如今的“红黑树”。
  • 它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色。
  • 每一个节点可以是红或者黑;红黑树不是高度平衡的,它的平衡是通过“红黑规则”进行实现的
  • 红黑树的增删改查性能都很好

红黑规则

  1. 每一个节点或是红色的,或是黑色的
  2. 根节点必须是黑色的
  3. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为 Nil,这些 Nil 视为叶节点,每个叶节点(Nil)是黑色的
  4. 如果某一个节点是红色的,那么它的子节点必须是黑色的(不能出现两个红色节点相连的情况)
  5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

屏幕截图 2026-03-24 204411

默认颜色:添加节点默认是红色的(效率高)

屏幕截图 2026-03-24 211725

Set 系列集合

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

Set 集合的实现类

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

Set 接口中的方法上基本上与 Collection 的 API 一致

练习:存储字符串并遍历

利用 Set 系列的集合,添加字符串,并使用多种方式遍历

  1. 迭代器
  2. 增强 for
  3. Lambda 表达式
package com.faxont.set;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SetDemo1 {
    public static void main(String[] args) {
        /*
            利用 Set 系统的集合,添加字符串,并使用多种方式遍历
            1. 迭代器
            2. 增强 for
            3. Lambda 表达式
         */

        // 1. 创建一个 Set 集合的对象
        Set<String> set = new HashSet<>();

        // 2. 添加元素
        // 如果当前元素是第一次添加,那么可以添加成功,返回 true
        // 如果当前元素是第二次添加,那么添加失败,返回 false
        System.out.println(set.add("aaa"));
        System.out.println(set.add("bbb"));
        System.out.println(set.add("bbb"));
        System.out.println(set.add("ccc"));


        // 3. 打印集合
        // 无序
        System.out.println(set);// [aaa, ccc, bbb]

        // 迭代器
//        Iterator<String> it = set.iterator();
//        while(it.hasNext()){
//            String s = it.next();
//            System.out.println(s);
//        }

        // 增强 for
//        for(String s : set){
//            System.out.println(s);
//        }

        // Lambda 遍历
        set.forEach(s -> System.out.println(s));
    }
}

HashSet

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

哈希表的组成

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

哈希值

哈希值:对象的整数表现形式

int index = (数组长度 - 1) & 哈希值;

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

对象哈希值特点

  • 如果没有重写 hashCode 方法,不同对象计算出的哈希值是不同的
  • 如果已经重写 hashCode 方法,不同对象只要属性值相同,计算出的哈希值就是一样的
  • 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞 / 哈希冲突)
package com.faxont.set;

public class HashSetDemo1 {
    public static void main(String[] args) {
        /*
            哈希值:
                对象哈希值特点
                1. 如果没有重写 hashCode 方法,不同对象计算出的哈希值是不同的
                2. 如果已经重写 hashCode 方法,不同对象只要属性值相同,计算出的哈希值就是一样的
                3. 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞 / 哈希冲突)
         */

        // 1. 创建对象
        Student s1 = new Student("zhangsan" ,23);
        Student s2 = new Student("zhangsan" ,23);

        // 2. 如果没有重写 hashCode 方法,不同对象计算出的哈希值是不同的
        System.out.println(s1.hashCode());// 990368553
        System.out.println(s2.hashCode());// 1096979270

        // 3. 如果已经重写 hashCode 方法,不同对象只要属性值相同,计算出的哈希值就是一样的
        System.out.println(s1.equals(s2));// true
        System.out.println(s1.hashCode() == s2.hashCode());// true
        System.out.println(s1.hashCode());// -1461067292
        System.out.println(s2.hashCode());// -1461067292

        // 4. 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞 / 哈希冲突)
        System.out.println("abc".hashCode());// 96354
        System.out.println("acD".hashCode());// 96354
    }
}
package com.faxont.set;

import java.util.Objects;

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

HashSet 底层原理

  1. 创建一个默认长度16,默认加载因子为0.75的数组,数组名 table
  2. 根据元素的哈希值跟数组的长度计算出应存入的位置
  3. 判断当前位置是否为 null,如果是 null 直接存入
  4. 如果位置不为 null,表示有元素,则调用 equals 方法比较属性值
  5. 一样:不存 不一样:存入数组,形成链表

​ JDK8 以前:新元素存入数组,老元素挂在新元素下面

​ JDK8 以后:新元素直接挂在老元素下面

  • 当 HashSet 内的元素达到长度 * 加载因子时,它的大小会扩容到原来的两倍
  • JDK8 以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树
  • 如果集合中存储的是自定义对象,必须要重写hashCode和equals方法

HashSet 的三个问题

问题1:HashSet 为什么存和取的顺序不一样?

问题2:HashSet 为什么没有索引?

问题3:HashSet 是利用什么机制保证数据去重的?

利用 HashSet 集合去除重复元素

需求:创建一个存储学生对象的集合,存储多个学生对象。

使用程序实现在控制台遍历该集合。

要求:学生对象的成员变量值相同,我们就认定是同一个对象

package com.faxont.set;

import java.util.HashSet;

public class HashSetDemo2 {
    public static void main(String[] args) {
        /*
            需求:创建一个存储学生对象的集合,存储多个学生对象。
            使用程序实现在控制台遍历该集合。
            要求:学生对象的成员变量值相同,我们就认定是同一个对象
         */

        // 1. 创建学生对象
        Student s1 = new Student("zhangsan" ,23);
        Student s2 = new Student("lisi" ,24);
        Student s3 = new Student("wangwu" ,25);
        Student s4 = new Student("zhangsan" ,23);

        // 2. 创建集合用来添加学生
        HashSet<Student> hs = new HashSet<>();

        // 3. 添加元素
        System.out.println(hs.add(s1));
        System.out.println(hs.add(s2));
        System.out.println(hs.add(s3));
        System.out.println(hs.add(s4));

        // 4. 打印集合
        System.out.println(hs);
    }
}

LinkedHashSet

LinkedHashSet 底层原理

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

import java.util.LinkedHashSet;

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        // 1. 创建4个学生对象
        Student s1 = new Student("zhangsan" ,23);
        Student s2 = new Student("lisi" ,24);
        Student s3 = new Student("wangwu" ,25);
        Student s4 = new Student("zhangsan" ,23);

        // 2. 创建集合的对象
        LinkedHashSet<Student> lhs = new LinkedHashSet<>();

        // 3. 添加元素
        System.out.println(lhs.add(s1));
        System.out.println(lhs.add(s2));
        System.out.println(lhs.add(s3));
        System.out.println(lhs.add(s4));

        System.out.println(lhs);
    }
}

TreeSet

  • 不重复、无索引、可排序
  • 可排序:按照元素的默认规则(从小到大)顺序。
  • TreeSet 集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。

TreeSet 对象排序练习题(1)

需求:存储整数并进行排序

package com.faxont.set;

import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo1 {
    public static void main(String[] args) {
        /**
         * 需求:利用 TreeSet 存储整数并进行排序
         */

        // 1. 创建 TreeSet 集合对象
        TreeSet<Integer> ts = new TreeSet<>();

        // 2. 添加元素
        ts.add(4);
        ts.add(5);
        ts.add(1);
        ts.add(3);
        ts.add(2);

        // 3. 打印集合
        System.out.println(ts);

        // 4. 遍历集合
        // 三种遍历方法都可以
        // 迭代器
        Iterator<Integer> it = ts.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        System.out.println("------------------");

        // 增强 for 循环
        for (Integer i : ts) {
            System.out.println(i);
        }
        System.out.println("------------------");
        
        // lambda 表达式
        ts.forEach(i -> System.out.println(i));// lambda
        System.out.println("------------------");
        /**
         * System.out::println 这种 类名::方法名 / 对象::方法名 写法,叫 方法引用(Method Reference)。
         *
         * 它是 Lambda 表达式的语法糖:
         * 当 Lambda 体只调用一个现成方法
         * 且参数顺序完全匹配
         * 就可以简写成 方法引用
         */
        ts.forEach(System.out::println);// 方法引用,简化的 lambda 表达式
    }
}

TreeSet 集合默认的规则

  • 对于数值类型:Integer。Double,默认按照从小到大的顺序进行排序。
  • 对于字符、字符串类型:按照字符在 ASCII 码表中的数字升序进行排序。

TreeSet 对象排序练习题(2)

需求:创建 TreeSet 集合,并添加3个学生对象

学生对象属性:姓名,年龄

要求按照学生的年龄进行排序

同年龄按照姓名字母排序(暂不考虑中文)

同姓名,同年龄认为是同一个人

package com.faxont.set;

import java.util.TreeSet;

public class TreeSetDemo2 {
    public static void main(String[] args) {
        /*
        需求:创建 TreeSet 集合,并添加3个学生对象
        学生对象属性:姓名,年龄
        要求按照学生的年龄进行排序
        同年龄按照姓名字母排序(暂不考虑中文)
        同姓名,同年龄认为是同一个人
         */

        // 1. 创建学生对象
        Student2 s1 = new Student2("zhangsan", 23);
        Student2 s2 = new Student2("lisi", 24);
        Student2 s3 = new Student2("wangwu", 25);
        Student2 s4 = new Student2("zhangsan", 23);

        // 2. 创建集合对象
        TreeSet<Student2> ts = new TreeSet<>();

        // 3. 添加元素
        System.out.println(ts.add(s3));
        System.out.println(ts.add(s1));
        System.out.println(ts.add(s2));
        System.out.println(ts.add(s4));

        // 4. 打印集合
        System.out.println(ts);

        // hashCode 和 equals 方法:哈希表有关的
        // TreeSet 底层是红黑树
    }
}
package com.faxont.set;

public class Student2 implements Comparable<Student2>{
    private String name;
    private int age;

    public Student2() {
    }

    public Student2(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student2{name = " + name + ", age = " + age + "}";
    }

    /**
     * this:表示当前要添加的元素
     * o:表示已经在红黑树中存在的元素
     *
     * 返回值:
     *      负数:this < o,认为要添加的元素是小的,存左边
     *      正数:this > o,认为要添加的元素是较大的,存右边
     *      0:this == o,认为要添加的元素跟已经存在的元素一样,不添加
     * @param o
     * @return
     */
    @Override
    public int compareTo(Student2 o) {
        System.out.println("-----------");
        System.out.println("this: " + this);
        System.out.println("o: " + o);
        // 指定排序的规则
        // 只看年龄,按照年龄的升序进行排序
        return this.getAge() - o.getAge();
    }
}

TreeSet 的两种比较方式

方式一:默认排序 / 自然排序:Javabean 类实现 Comparable 接口指定比较规则

详情可看练习(2)

方式二:比较器排序:创建 TreeSet 对象时,传递比较器 Comparator 指定规则

详情可看练习(3)

使用原则:默认使用第一种,如果第一种不能满足当前需求,就使用第二种

当方式一二同时存在时,以方式二为准

TreeSet 对象排序练习题(3)

需求:请自行选择比较强排序和自然排序两种方式;

要求:存入四个字符串,"c","ab","df","qwer"

​ 按照长度排序,如果一样长则按照首字母排序

package com.faxont.set;

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetDemo3 {
    public static void main(String[] args) {
        /*
        需求:请自行选择比较强排序和自然排序两种方式;
        要求:存入四个字符串,"c","ab","df","qwer"
        按照长度排序,如果一样长则按照首字母排序
         */

        // 1. 创建集合
        /**
         * o1:表示要添加的元素
         * o2:表示已经在红黑树的元素
         * 返回值规则跟之前一样
         */
        TreeSet<String> ts = new TreeSet<>((o1, o2) -> {
                // 按照长度排序
                int i = o1.length() - o2.length();
                // 如果长度一样,按照首字母排序
                i = i == 0 ? o1.compareTo(o2) : i;
                return i;
        });

        // 2. 添加元素
        ts.add("c");
        ts.add("ab");
        ts.add("df");
        ts.add("qwer");

        // 3. 打印集合
        System.out.println(ts);
    }
}

TreeSet 对象排序练习题(4)

需求:创建5个学生对象

属性:(姓名,年龄,语文成绩,数学成绩,英语成绩)。

按照总分从高到低输出到控制台

如果总分一样,按照语文成绩排

如果语文一样,按照数学成绩排

如果数学一样,按照英语成绩排

如果英语一样,按照年龄排

如果年龄一样,按照姓名的字母顺序排

如果都一样,认为是同一个学生,不存

package com.faxont.set;

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetDemo4 {
    public static void main(String[] args) {
        /*
        需求:创建5个学生对象
        属性:(姓名,年龄,语文成绩,数学成绩,英语成绩)。
        按照总分从高到低输出到控制台
        如果总分一样,按照语文成绩排
        如果语文一样,按照数学成绩排
        如果数学一样,按照英语成绩排
        如果英语一样,按照年龄排
        如果年龄一样,按照姓名的字母顺序排
        如果都一样,认为是同一个学生,不存
         */

        // 1. 创建5个学生对象
        Student3 s1 = new Student3("zhangsan", 23, 90, 99, 50);
        Student3 s2 = new Student3("lisi", 24, 90, 98, 50);
        Student3 s3 = new Student3("wangwu", 25, 95, 100, 30);
        Student3 s4 = new Student3("zhaoliu", 26, 60, 99, 70);
        Student3 s5 = new Student3("qianqi", 26, 70, 80, 70);

        // 2. 创建集合对象
        /**
         * 默认使用 ArrayList(最常见)
         * 想要数据唯一,在 Set 里面选,默认选择 HashSet
         * 既要唯一,又要排序,使用 TreeSet
         */
        TreeSet<Student3> ts = new TreeSet<>();

        // 3. 添加元素
        System.out.println(ts.add(s1));
        System.out.println(ts.add(s2));
        System.out.println(ts.add(s3));
        System.out.println(ts.add(s4));
        System.out.println(ts.add(s5));

        // 4. 遍历集合
        for (Student3 t : ts) {
            int sum = t.getChinese() + t.getMath() + t.getEnglish();
            System.out.println("总分为: " + sum);
            System.out.println(t);
        }
    }
}
package com.faxont.set;

public class Student3 implements Comparable<Student3>{
    private String name;
    private int age;
    private int chinese;
    private int math;
    private int english;

    public Student3() {
    }

    public Student3(String name, int age, int chinese, int math, int english) {
        this.name = name;
        this.age = age;
        this.chinese = chinese;
        this.math = math;
        this.english = english;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 获取
     * @return chinese
     */
    public int getChinese() {
        return chinese;
    }

    /**
     * 设置
     * @param chinese
     */
    public void setChinese(int chinese) {
        this.chinese = chinese;
    }

    /**
     * 获取
     * @return math
     */
    public int getMath() {
        return math;
    }

    /**
     * 设置
     * @param math
     */
    public void setMath(int math) {
        this.math = math;
    }

    /**
     * 获取
     * @return english
     */
    public int getEnglish() {
        return english;
    }

    /**
     * 设置
     * @param english
     */
    public void setEnglish(int english) {
        this.english = english;
    }

    public String toString() {
        return "Student3{name = " + name + ", age = " + age + ", chinese = " + chinese + ", math = " + math + ", english = " + english + "}";
    }

    /*
    按照总分从高到低输出到控制台
        如果总分一样,按照语文成绩排
        如果语文一样,按照数学成绩排
        如果数学一样,按照英语成绩排
        如果英语一样,按照年龄排
        如果年龄一样,按照姓名的字母顺序排
        如果都一样,认为是同一个学生,不存
     */
    @Override
    public int compareTo(Student3 o) {
        int thisScore = this.getChinese() + this.getMath() + this.getEnglish();
        int oScore = o.getChinese() + o.getMath() + o.getEnglish();
        int i = thisScore > oScore ? -1 : 1;
        i = i == 0 ? this.getChinese() - o.getChinese() : i;
        i = i == 0 ? this.getMath() - o.getMath() : i;
        i = i == 0 ? this.getEnglish() - o.getEnglish() : i;// 可以去掉,因为总分、语文、数学都一样,那么英语一定一样
        i = i == 0 ? this.getAge() - o.getAge() : i;
        i = i == 0 ? this.getName().compareTo(o.getName()) : i;
        return i;
    }
}

单列集合的应用场景

  1. 如果想要集合中的元素可重复
  • 用 ArrayList 集合,基于数组的。(用的最多)
  1. 如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
  • 用 LinkedList 集合,基于链表的。
  1. 如果想对集合中的元素去重
  • 用 HashSet 集合,基于哈希表的。(用的最多)
  1. 如果想对集合中的元素去重,而且保证存取顺序
  • 用 LinkedHashSet 集合,基于哈希表和双链表,效率低于 HashSet。
  1. 如果想对集合中的元素进行排序
  • 用 TreeSet 集合,基于红黑树。后续也可以用 List 集合实现排序。

双列集合的特点

  1. 双列集合一次需要存一对数据,分别为键和值
  2. 键不能重复,值可以重复
  3. 键和值是一一对应的,每一个键只能找到自己对应的值
  4. 键 + 值 这个整体我们称之为 “键值对” 或者 “键值对对象”,在 Java 中叫做 “Entry对象”

Map

Map 的常见API

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() 集合的长度,也就是集合中键值对的个数
package com.faxont.map;

import java.util.HashMap;
import java.util.Map;

public class MapDemo1 {
    public static void main(String[] args) {
        /*
            V put(K key, V value)                       添加元素
            V remove(Object key)                        根据键删除键值对元素
            void clear()                                移除所有的键值对元素
            boolean containsKey(Object key)             判断集合是否包含指定的键
            boolean containsValue(Object value)         判断集合是否包含指定的值
            boolean isEmpty()                           判断集合是否为空
            int size()                                  集合的长度,也就是集合中键值对的个数
         */

        // 1. 创建 Map 集合对象
        Map<String, String> m = new HashMap<>();

        // 2. 添加元素
        // 有两个功能    添加 / 覆盖
        // 在添加数据的时候,如果键不存在,那么直接把键值对对象添加到 map 集合当中,会返回 null
        // 在添加数据的时候,如果键是存在的,那么会把原有的键值对对象覆盖,会把被覆盖的对象返回
        String value1 = m.put("Kindred", "千珏");
//        System.out.println(value1);// null
        m.put("Faxont", "弋湖");
        m.put("Scroll", "卷頔");

//        String value2 = m.put("Kindred", "镜爪");// 直接覆盖了上面的键值对
//        System.out.println(value2);// 千珏     返回了被覆盖的键值对的值

        // 删除
//        String result = m.remove("Kindred");
//        System.out.println(result);// 千珏

        // 清空
//        m.clear();

        // 判断是否包含
//        boolean keyResult = m.containsKey("Kindred");
//        System.out.println(keyResult);
//        boolean valueResult = m.containsValue("千珏");
//        System.out.println(valueResult);

        // 判断是否为空
//        System.out.println(m.isEmpty());

        int size = m.size();
        System.out.println(size);

        // 3. 打印集合
        System.out.println(m);
    }
}

Map 的遍历方式

  1. 键找值
package com.faxont.map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class MapDemo2 {
    public static void main(String[] args) {
        // 1. 创建 Map 对象
        Map<String, String> m = new HashMap<>();

        // 2.添加元素
        m.put("Kindred", "千珏");
        m.put("Faxont", "弋湖");
        m.put("Scroll", "卷頔");

        // 3. 通过键找值
        // 3.1 获取所有的键,把这些键放到一个单列集合当中
        Set<String> s = m.keySet();
        // 3.2 遍历单列集合,得到每一个键
        // 增强 for 遍历
//        for (String key : s) {
//            // 3.3 通过键获取对应的值
//            String value = m.get(key);
//            System.out.println(key + "=" + value);
//        }

        // 迭代器遍历
//        Iterator it = s.iterator();
//        while (it.hasNext()){
//            String key = (String) it.next();
//            String value = m.get(key);
//            System.out.println(key + "=" + value);
//        }

        // Lambda 表达式遍历
        s.forEach(key -> System.out.println(key + "=" + m.get(key)));
    }
}
  1. 键值对
package com.faxont.map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class MapDemo3 {
    public static void main(String[] args) {
        // 1. 创建 Map 集合对象
        Map<String, String> m = new HashMap<>();

        // 2. 添加元素
        m.put("Kindred", "千珏");
        m.put("Faxont", "弋湖");
        m.put("Scroll", "卷頔");

        // 3. 通过键值对对象进行遍历
        // 3.1 通过一个方法获取所有的键值对对象,返回一个 Set 集合
        Set<Map.Entry<String, String>> entries = m.entrySet();
        // 3.2 遍历 entries 这个对象,得到每个键值对对象
        // 增强 for 遍历
//        for (Map.Entry<String, String> entry : entries) {
//            // 3.3 利用 entry 调用 get 方法获取键值对对象的键和值
//            String key = entry.getKey();
//            String value = entry.getValue();
//            System.out.println(key + "=" + value);
//        }

        // 迭代器遍历
//        Iterator<Map.Entry<String, String>> it = entries.iterator();
//        while (it.hasNext()){
//            Map.Entry<String, String> entry = it.next();
//            String key = entry.getKey();
//            String value = entry.getValue();
//            System.out.println(key + "=" + value);
//        }

        // Lambda 遍历
        entries.forEach(entry -> System.out.println(entry.getKey() + "=" + entry.getValue()));
    }
}
  1. Lambda 表达式
方法名称 说明
default void forEach(BiConsumer<? super K, ? super V> action) 结合 lambda 遍历 Map 集合
package com.faxont.map;

import java.util.HashMap;
import java.util.Map;

public class MapDemo4 {
    public static void main(String[] args) {
        // 1. 创建 Map 集合对象
        Map<String, String> m = new HashMap<>();

        // 2. 添加元素
        m.put("Kindred", "千珏");
        m.put("Faxont", "弋湖");
        m.put("Scroll", "卷頔");

        // 3. 利用 lambda 表达式进行遍历
        // 底层:
        // forEach 其实就是利用第二种方式进行遍历,依次得到每一个键和值
        // 再调用 accept 方法
        m.forEach((key, value) -> System.out.println(key + "=" + value));
    }
}

HashMap

HashMap 的特点

  1. HashMap 是 Map 里面的一个实现类。
  2. 没有额外需要学习的特有方法,直接使用 Map 里面的方法就可以了。
  3. 特点都是由键决定的:无序、不重复、无索引
  4. HashMap 跟 HashSet 底层原理是一模一样的,都是哈希表结构

总结

  1. HashMap 底层是哈希表结构的
  2. 依赖 hashCode 方法和 equals 方法保证键的唯一
  3. 如果键存储的是自定义对象,需要重写 hashCode 和 equals 方法

​ 如果值存储自定义对象,不需要重写 hashCode 和 equals 方法

练习1:存储学生对象并遍历

需求

创建一个 HashMap 集合,键是学生对象(Student),值是籍贯(String)。

存储三个键值对元素,并遍历

要求:同姓名,同年龄认为是同一个学生

package com.faxont.map;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapDemo1 {
    public static void main(String[] args) {
        /*
            需求
            创建一个 HashMap 集合,键是学生对象(Student),值是籍贯(String)。
            存储三个键值对元素,并遍历
            要求:同姓名,同年龄认为是同一个学生
         */

        // 1. 创建 HashMap 集合对象
        HashMap<Student, String> hm = new HashMap<>();

        // 2. 创建学生对象
        Student s1 = new Student("zhangsan", 23);
        Student s2 = new Student("lisi", 24);
        Student s3 = new Student("wangwu", 25);
        Student s4 = new Student("zhangsan", 23);

        // 3. 添加元素
        hm.put(s1, "北京");
        hm.put(s2, "上海");
        hm.put(s3, "广州");
        hm.put(s4, "深圳");

        // 4. 遍历集合
        Set<Student> students = hm.keySet();
        for (Student key : students) {
            String value = hm.get(key);
            System.out.println(key + "=" + value);
        }

        System.out.println("-------------------");

        Set<Map.Entry<Student, String>> entries = hm.entrySet();
        for (Map.Entry<Student, String> entry : entries) {
            Student key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "=" + value);
        }

        System.out.println("-------------------");
        hm.forEach((key, value) -> System.out.println(key + "=" + value));
    }
}
package com.faxont.map;

import java.util.Objects;

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

练习2:统计投票人数

需求

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

package com.faxont.map;

import java.util.*;

public class HashMapDemo2 {
    public static void main(String[] args) {
        /*
            需求
            某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A, B, C, D),
            每个学生只能选择一个景点,请统计出最终哪个景点想去的人最多。
         */

        // 1. 先获取学生的投票情况
        // 定义一个数组,存储4个景点
        String[] arr = {"A", "B", "C", "D"};
        // 利用随机数模拟80个同学的投票,并把投票结果存储起来
        ArrayList<String> list = new ArrayList<>();
        Random r = new Random();
        for (int i = 0; i < 80; i++) {
            int index = r.nextInt(arr.length);
            list.add(arr[index]);
        }

        // 2. 如果要统计的对象比较多,不方便使用计数器思想
        // 我们可以定义 map 集合,利用集合进行统计
        HashMap<String, Integer> hm = new HashMap<>();

        for (String name : list) {
            // 判断当前景点在 map 中是否存在
            if(hm.containsKey(name)){
                // 存在,则获取当前景点的票数
                Integer count = hm.get(name);
                // 票数 + 1
                count++;
                // 存储到 map 中
                hm.put(name, count);
            }else{
                // 不存在,则添加到 map 中
                hm.put(name, 1);
            }
        }
        System.out.println(hm);

        // 3. 求最大值
        Set<Map.Entry<String, Integer>> entries = hm.entrySet();
        int maxCount = 0;
        for (Map.Entry<String, Integer> entry : entries) {
            int count = entry.getValue();
            if(count > maxCount){
                maxCount = count;
            }
        }

        // 4. 判断哪个景点的票数跟最大值一样,一样则打印出来
        for (Map.Entry<String, Integer> entry : entries) {
            if(entry.getValue() == maxCount){
                System.out.println(entry.getKey() + "景点想去的人最多");
            }
        }
    }
}

LinkedHashMap

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

import java.util.LinkedHashMap;

public class LinkedHashMapDemo1 {
    public static void main(String[] args) {
        /*
            由键决定:有序、不重复、无索引。
            这里的有序指的是保证存储和取出的元素顺序一致。
            原理:底层数据结构依然是哈希表,只是每个键值对元素又额外多了一个双链表机制记录存储的顺序。
         */

        // 1. 创建 LinkedHashMap 集合对象
        LinkedHashMap<String, Integer> lhm = new LinkedHashMap<>();

        // 2. 添加元素
        lhm.put("a", 123);
        lhm.put("b", 456);
        lhm.put("a", 123);
        lhm.put("a", 111);
        lhm.put("c", 789);

        // 3. 打印集合
        System.out.println(lhm);
    }
}

TreeMap

  • TreeMap 跟 TreeSet 底层原理一样,都是红黑树结构
  • 由键决定特性:不重复、无索引、可排序
  • 可排序:对键进行排序
  • 注意:默认按照键从小到大进行排序,也可以直接规定键的排序规则

代码书写两种排序规则

  • 实现 Comparable 接口,指定比较规则
  • 创建集合时传递 Comparator 比较器对象,指定比较规则

TreeMap 基本应用

需求1:

键:整数表示 id

值:字符串表示商品名称

要求:按照 id 的升序排列、按照 id 的降序排列

package com.faxont.map;

import java.util.Comparator;
import java.util.TreeMap;

public class TreeMapDemo1 {
    public static void main(String[] args) {
        /*
            需求1:
            键:整数表示 id
            值:字符串表示商品名称
            要求:按照 id 的升序排列、按照 id 的降序排列
         */

        // 1. 创建 TreeMap

        // Integer Double 默认情况下是按照升序排列的
        // String 按照字符在 ASCII 码值进行升序排列
        TreeMap<Integer, String> tm = new TreeMap<>(new Comparator<Integer>(){
            @Override
            public int compare(Integer o1, Integer o2) {
                // o1:当前要添加的元素
                // o2:已经存在的元素
                // o1 - o2:升序排列
                // o2 - o1:降序排列
                return o2 - o1;
            }
        });

        // 2. 添加元素
        tm.put(1, "巫妖");
        tm.put(3, "军团");
        tm.put(2, "护士");
        tm.put(4, "幽灵");
        tm.put(5, "怨灵");

        // 3. 打印集合
        System.out.println(tm);
    }
}

需求2:

键:学生对象

值:籍贯

要求:按照学生年龄的升序排列,年龄一样按照姓名的字母排列,同姓名年龄视为同一个人

package com.faxont.map;

import java.util.TreeMap;

public class TreeMapDemo2 {
    public static void main(String[] args) {
        /*
            需求2:
            键:学生对象
            值:籍贯
            要求:按照学生年龄的升序排列,年龄一样按照姓名的字母排列,同姓名年龄视为同一个人
         */

        // 1. 创建 TreeMap 集合对象
        TreeMap<Student2, String> tm = new TreeMap<>();

        // 2. 创建学生对象
        Student2 s1 = new Student2("lisi", 23);
        Student2 s2 = new Student2("zhangsan", 24);
        Student2 s3 = new Student2("wangwu", 25);
        Student2 s4 = new Student2("lisi", 23);
        Student2 s5 = new Student2("qianqi",23);

        // 3. 添加元素
        tm.put(s1, "上海");
        tm.put(s2, "北京");
        tm.put(s3, "广州");
        tm.put(s4, "深圳");
        tm.put(s5, "杭州");

        // 4. 遍历集合
        tm.forEach((key, value) -> System.out.println(key + "=" + value));
    }
}
package com.faxont.map;

public class Student2 implements Comparable<Student2>{
    private String name;
    private int age;

    public Student2() {
    }

    public Student2(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student2{name = " + name + ", age = " + age + "}";
    }

    @Override
    public int compareTo(Student2 o) {
        int i = this.getAge() - o.getAge();
        i = i == 0 ? this.getName().compareTo(o.getName()) : i;
        return i;
    }
}

练习:统计个数

需求:字符串 “aababcabcdabcde”

​ 请统计字符串中每一个字符出现的次数,并按照以下格式输出

​ 输出结果:

​ a (5) b (4) c (3) d (2) e (1)

package com.faxont.map;

import java.util.*;

public class TreeMapDemo3 {
    public static void main(String[] args) {
        /*
            需求:字符串 “aababcabcdabcde”
            请统计字符串中每一个字符出现的次数,并按照以下格式输出
            输出结果:
                a (5) b (4) c (3) d (2) e (1)

            新的统计思想:利用 map 集合进行统计

            如果题目中没有要求对结果进行排序,默认使用 HashMap
            如果题目中要求对结果进行排序,则使用 TreeMap

            键:表示要统计的内容
            值:表示次数
         */

        // 1. 创建字符串
        String str = "aababcabcdabcde";

        // 2. 创建集合
        TreeMap<Character, Integer> tm = new TreeMap<>();

        // 3. 遍历字符串获取字符
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            // 拿着字符去集合中判断字符是否存在
            // 如果存在,则获取字符的次数,次数 + 1
            // 如果不存在,则次数为 1
            if(tm.containsKey(c)){
                Integer count = tm.get(c);
                count++;
                tm.put(c, count);
            }else{
                tm.put(c, 1);
            }
        }

        // 4. 遍历集合,按照要求输出结果
//        for (Map.Entry<Character, Integer> entry : tm.entrySet()) {
//            System.out.print(entry.getKey() + " (" + entry.getValue() + ") ");
//        }

        // 拼接字符串
//        StringBuilder sb = new StringBuilder();
//        tm.forEach((key, value) -> sb.append(key).append(" (").append(value).append(") "));
//        System.out.println(sb);

        StringJoiner sj = new StringJoiner("", "", "");
        tm.forEach((key, value) -> sj.add(key + "").add(" (").add(value + "").add(") "));
        System.out.println(sj);
    }
}

可变参数的练习

假如需要定义一个方法求和,该方法可以灵活地完成如下需求:

计算2个数据的和

计算3个数据的和

计算4个数据的和

计算n个数据的和

package com.faxont.args;

public class ArgsDemo1 {
    public static void main(String[] args) {
        /*
            假如需要定义一个方法求和,该方法可以灵活地完成如下需求:
            计算2个数据的和
            计算3个数据的和
            计算4个数据的和
            计算n个数据的和
         */

        System.out.println(getSum(10, 20));
        System.out.println(getSum(10, 20, 30));
        System.out.println(getSum(10, 20, 30, 40));
    }

    // 可变参数的小细节:
    // 1. 在方法的形参中最多只能写一个可变参数
    // 2. 可变参数必须放在形参列表的最后
    public static int getSum(int n, int ... args){
        int sum = n;
        for(int i = 0; i < args.length; i++){
            sum += args[i];// 可变参数的本质是数组
        }
        return sum;
    }
}

Collections

  • java.util.Collections:是集合工具类
  • 作用:Collections 不是集合,而是集合的工具类

Collections 常用的 API

方法名称 说明
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 list, Comparator c) 根据指定的规则进行排序
public static int binarySearch(List list, T key) 以二分查找法查找元素
public static void copy(List dest, List src) 拷贝集合中的元素
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) 交换集合中指定位置的元素
package com.faxont.collections;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class CollectionsDemo1 {
    public static void main(String[] args) {
        /*
            public static <T> boolean addAll(Collection<T> c, T ... elements)   批量添加元素
            public static void shuffle(List<?> list)                            打乱 List 集合元素的顺序
            public static <T> void sort(List<T> list)                           排序
            public static <T> void sort(List<T> list, Comparator<T> c           根据指定的规则进行排序
            public static <T> int binarySearch(List<T> list, T key)             以二分查找法查找元素
            public static <T> void copy(List<T> dest, List<T> src)              拷贝集合中的元素
            public static <T> int fill(List<T> list, T obj)                     使用指定的元素填充集合
            public static <T> void max / min(Collection<T> coll)                根据默认的自然排序获取最大 / 小值
            public static <T> void swap(List<?> list, int i, int j)             交换集合中指定位置的元素
        */

        // addAll 批量添加元素

        // 1. 创建集合对象
        ArrayList<String> list = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();

        // 2. 批量添加元素
        Collections.addAll(list, "Kindred", "Scroll", "Faxont", "Dead By Daylight", "Tomb Raider");

        // 3. 打印集合
        System.out.println(list);
        System.out.println("-------------");

        // 打乱 List 集合元素的顺序
        Collections.shuffle(list);
        System.out.println(list);
        System.out.println("-------------");

        // 排序
        Collections.sort(list);
        System.out.println(list);
        System.out.println("-------------");

        // 根据指定的规则进行排序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length() - o2.length();
            }
        });
        System.out.println(list);
        System.out.println("-------------");

        // 以二分查找法查找元素
        int index = Collections.binarySearch(list, "Tomb Raider");
        System.out.println(index);
        System.out.println(list.get(index));
        System.out.println("-------------");

        // 拷贝集合中的元素
        // 先给 list2 填满占位,让长度足够
        // 因为 copy 不会自动扩容
        for (int i = 0; i < list.size(); i++) {
            list2.add(null);
        }
        Collections.copy(list2, list);
        System.out.println(list2);
        System.out.println("-------------");

        // 使用指定的元素填充集合
        Collections.fill(list2, "Faxont");
        System.out.println(list2);
        System.out.println("-------------");

        // 根据默认的自然排序获取最大 / 小值
        System.out.println(Collections.max(list));
        System.out.println(Collections.min(list));
        System.out.println("-------------");

        // 交换集合中指定位置的元素
        Collections.swap(list, 0, 2);
        System.out.println(list);
    }
}

综合练习

练习1:自动点名1

班级里有 N 个学生,实现随机点名器。

package com.faxont.test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class Test1 {
    public static void main(String[] args) {
        /*
            班级里有 N 个学生,学生属性:姓名、年龄、性别,实现随机点名器
         */

        // 1. 定义集合
        ArrayList<String> list = new ArrayList<>();

        // 2. 添加元素
        Collections.addAll(list, "范闲", "范建", "杜子腾", "杜琦燕", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛");

        // 3. 随机点名
        // 创建随机数对象
//        Random r = new Random();
//        int index = r.nextInt(list.size());
//        System.out.println(list.get(index));

        // 打乱集合,获取 0 索引处的元素
        Collections.shuffle(list);
        System.out.println(list.get(0));
    }
}

练习2:自动点名器2

班级里有 N 个学生

要求:

70%的概率随机到男生

30%的概率随机到女生

package com.faxont.test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class Test2 {
    public static void main(String[] args) {
        /*
            班级里有 N 个学生
            要求:
            70%的概率随机到男生
            30%的概率随机到女生

            "范闲", "范建", "杜子腾", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰",
             "杜琦燕", "袁明媛", "李猜", "田蜜蜜"
         */

        // 1. 定义集合
        ArrayList<Integer> list = new ArrayList<>();

        // 2. 添加元素
        Collections.addAll(list, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0);

        // 3. 随机点名,点到1选择男生,点到0选择女生
        Random r = new Random();
        int number = r.nextInt(list.size());

        // 4. 创建两个集合分别存储男女生的数据
        ArrayList<String> boyList = new ArrayList<>();
        ArrayList<String> girlList = new ArrayList<>();

        // 5. 添加数据
        Collections.addAll(boyList, "范闲", "范建", "杜子腾", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰");
        Collections.addAll(girlList, "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛", "李猜", "田蜜蜜");
        if (number == 1) {
            System.out.println("男生");
            System.out.println(boyList.get(r.nextInt(boyList.size())));
        } else {
            System.out.println("女生");
            System.out.println(girlList.get(r.nextInt(girlList.size())));
        }
    }
}

练习3:自动点名器3

班级里有N个学生

要求:

被点到的学生不会再被点到。

但是如果班级中所有的学生都点完了,需要重新开启第二轮点名。

package com.faxont.test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class Test3 {
    public static void main(String[] args) {
        /*
            班级里有N个学生
            要求:
            被点到的学生不会再被点到。
            但是如果班级中所有的学生都点完了,需要重新开启第二轮点名。
         */

        // 1. 定义集合
        ArrayList<String> list = new ArrayList<>();

        // 2. 添加元素
        Collections.addAll(list, "范闲", "范建", "杜子腾", "杜琦燕", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛");

        // 3. 随机点名
        // 创建随机数对象
        Random r = new Random();
        rollCall(list, r, 2);
    }

    private static void rollCall(ArrayList<String> list, Random r, int count) {
        for (int i = 0; i < count; i++) {
            ArrayList<String> l = new ArrayList<>();
            l.addAll(list);
            while(!list.isEmpty()){
                System.out.println(list.remove(r.nextInt(list.size())));
            }
            System.out.println(
                    i == count - 1
                            ? "第" + (i + 1) + "轮点名已结束,所有点名已完成!"
                            : "第" + (i + 1) + "轮点名已结束,接下来进行第" + (i + 2) + "轮点名..."
            );
            list.addAll(l);
            l.clear();
        }
    }
}

练习4:自动点名器4

TxT 文件中事先准备好80个学生姓名,每个学生的名字独占一行。

要求1:每次被点到的学生,再次被点到的概率在原先的基础上降低一半。

举例:80个学生,点名5次,每次都点到小A,概率变化情况如下:

​ 第一次每人概率:1.25%

​ 第二次小A概率:0.625% 其它学生概率:1.2579%

​ 第三次小A概率:0.3125% 其它学生概率:1.261867%

​ 第四次小A概率:0.15625% 其它学生概率:1.2638449%

​ 第五次小A概率:0.078125% 其它学生概率:1.26483386%

要求2:作弊要求,第三次点名一定是张三

提示:本题需要用到集合,IO,权重随机算法

练习5:自动点名器5

image-20260409210116269

练习6:Map 集合案例-省和市

需求

定义一个 Map 集合,键用于表示省份名称 province,值表示市 city,但是市会有多个。

添加完毕后,遍历结果格式如下:

​ 江苏省 = 南京市, 扬州市, 苏州市, 无锡市, 常州市

​ 湖北省 = 武汉市, 孝感市, 十堰市, 宜昌市, 鄂州市

​ 河北省 = 石家庄市, 唐山市, 邢台市, 保定市, 张家口市

package com.faxont.test;

import java.util.*;

public class Test4 {
    public static void main(String[] args) {
        /*
            定义一个 Map 集合,键用于表示省份名称 province,值表示市 city,但是市会有多个。
            添加完毕后,遍历结果格式如下:
                    江苏省 = 南京市, 扬州市, 苏州市, 无锡市, 常州市
                    湖北省 = 武汉市, 孝感市, 十堰市, 宜昌市, 鄂州市
                    河北省 = 石家庄市, 唐山市, 邢台市, 保定市, 张家口市
         */

        // 1. 创建 Map 集合
        HashMap<String, ArrayList<String>> hm = new HashMap<>();

        // 2. 创建单列集合存储城市
        ArrayList<String> jiangsu = new ArrayList<>();
        ArrayList<String> hubei = new ArrayList<>();
        ArrayList<String> hebei = new ArrayList<>();
        Collections.addAll(jiangsu, "南京市", "扬州市", "苏州市", "无锡市", "常州市");
        Collections.addAll(hubei, "武汉市", "孝感市", "十堰市", "宜昌市", "鄂州市");
        Collections.addAll(hebei, "石家庄市", "唐山市", "邢台市", "保定市", "张家口市");

        // 3. 添加元素
        hm.put("江苏省", jiangsu);
        hm.put("湖北省", hubei);
        hm.put("河北省", hebei);

        // 4. 遍历得到结果
        Set<Map.Entry<String, ArrayList<String>>> entrySet = hm.entrySet();
        for (Map.Entry<String, ArrayList<String>> entry : entrySet) {
            String province = entry.getKey();
            ArrayList<String> city = entry.getValue();
            StringJoiner sj = new StringJoiner(", ");
            city.forEach(sj::add);
            System.out.println(province + " = " + sj);
        }
    }
}

斗地主小游戏

package com.faxont.pokerGame;

public class App {
    public static void main(String[] args) {
        /*
            完成控制台的三步:
                准备牌
                洗牌
                发牌

                "♦", "♣", "❤", "♠"
                "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
         */

        new PokerGame();
    }
}
package com.faxont.pokerGame;

import java.util.ArrayList;
import java.util.Collections;

public class PokerGame {

    // 牌盒
    static ArrayList<String> list = new ArrayList<>();

    // 准备牌
    // 静态代码块
    // 特点:随着类的加载而加载,而且只执行一次
    static {
        // 准备牌
        // "♦", "♣", "❤", "♠"
        // "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
        String[] colors = {"♦", "♣", "❤", "♠"};
        String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};

        for (String color : colors) {
            for (String number : numbers) {
                list.add(color + number);
            }
        }

        list.add("B_Joker");
        list.add("R_Joker");
    }

    public PokerGame() {

        // 洗牌
        Collections.shuffle(list);

        // 发牌
        ArrayList<String> lord = new ArrayList<>();
        ArrayList<String> player1 = new ArrayList<>();
        ArrayList<String> player2 = new ArrayList<>();
        ArrayList<String> player3 = new ArrayList<>();

        // 遍历牌盒得到每一张牌
        for (int i = 0; i < list.size(); i++) {
            String poker = list.get(i);
            if(i <= 2){
                lord.add(poker);
                continue;
            }

            // 给三个玩家轮流发牌
            // i % 3 == 0   player1
            // i % 3 == 1   player2
            // i % 3 == 2   player3
            if(i % 3 == 0){
                player1.add(poker);
            }else if(i % 3 == 1){
                player2.add(poker);
            }else{
                player3.add(poker);
            }
        }

        // 看牌
        lookPoker("底牌", lord);
        lookPoker("玩家1", player1);
        lookPoker("玩家2", player2);
        lookPoker("玩家3", player3);

    }

    /**
     *
     * @param name 玩家名称
     * @param list 玩家牌组
     */
    public void lookPoker(String name, ArrayList<String> list){
        System.out.print(name + ": ");
        for (String poker : list) {
            System.out.print(poker + " ");
        }
        System.out.println();
    }

}

方式一:利用序号进行排序

  • 如果原始数据的规律非常复杂,我们可以先手动排序让每一个数据跟唯一的序号产生对应关系。
  • 序号就是数字,规律非常简单,后续的所有操作,我们以序号为准。
  • 当真正需要操作原始数据的时候,再通过序号找到原始数据即可。
package com.faxont.pokerGame2;

public class App {
    public static void main(String[] args) {
        /*
            完成控制台的三步:
                准备牌
                洗牌
                发牌

                "♦", "♣", "❤", "♠"
                "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
         */
        new PokerGame();
    }
}
package com.faxont.pokerGame2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet;

public class PokerGame {

    // 牌盒 Map
    // 此时我们只要把牌跟序号产生对应关系就可以了,不需要按照序号进行排序,所以只要 HashMap 就可以了
    static HashMap<Integer, String> hm = new HashMap<>();
    static ArrayList<Integer> list = new ArrayList<>();

    static{
        String[] colors = {"♦", "♣", "♥", "♠"};
        String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};

        // 序号
        int serialNumber = 1;
        // 细节
        for(String number : numbers){
            // 依次表示每一个数字
            for(String color : colors){
                // 依次表示每一个花色
                hm.put(serialNumber, color + number);
                list.add(serialNumber);
                serialNumber++;
            }
        }

        hm.put(serialNumber, "B_Joker");
        list.add(serialNumber);
        hm.put(serialNumber + 1, "R_Joker");
        list.add(serialNumber + 1);
    }

    public PokerGame(){
        // 洗牌
        Collections.shuffle(list);

        // 发牌
        TreeSet<Integer> lord = new TreeSet<>();
        TreeSet<Integer> player1 = new TreeSet<>();
        TreeSet<Integer> player2 = new TreeSet<>();
        TreeSet<Integer> player3 = new TreeSet<>();

        for (int i = 0; i < list.size(); i++) {
            // i:依次表示集合中的每一个索引
            int serialNumber = list.get(i);

            if(i <= 2){
                lord.add(serialNumber);
                continue;
            }
            if(i % 3 == 0){
                player1.add(serialNumber);
            }else if(i % 3 == 1){
                player2.add(serialNumber);
            }else{
                player3.add(serialNumber);
            }
        }

        // 看牌
        lookPoker("底牌", lord);
        lookPoker("玩家1", player1);
        lookPoker("玩家2", player2);
        lookPoker("玩家3", player3);

    }

    /**
     *
     * @param name 玩家名称
     * @param list 玩家牌组
     */
    public void lookPoker(String name, TreeSet<Integer> list){
        System.out.print(name + ": ");
        for (int serialNumber : list) {
            System.out.print(hm.get(serialNumber) + " ");
        }
        System.out.println();
    }
}

方式二:给每一张牌计算价值

package com.faxont.pokerGame3;

public class App {
    public static void main(String[] args) {
        /*
            完成控制台的三步:
                准备牌
                洗牌
                发牌

                "♦", "♣", "❤", "♠"
                "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
         */
        new PokerGame();
    }
}
package com.faxont.pokerGame3;

import java.util.*;

public class PokerGame {

    // 牌盒
    static HashMap<String, Integer> hm = new HashMap<>();
    static ArrayList<String> list = new ArrayList<>();

    // 准备牌
    // 静态代码块
    // 特点:随着类的加载而加载,而且只执行一次
    static {
        // 准备牌
        // "♦", "♣", "❤", "♠"
        // "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
        String[] colors = {"♦", "♣", "❤", "♠"};
        String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};

        for (String color : colors) {
            for (String number : numbers) {
                list.add(color + number);
            }
        }

        list.add("B_Joker");
        list.add("R_Joker");

        // 指定牌的价值
        // 牌上的数字到 Map 集合中判断是否存在
        // 存在,获取价值
        // 不存在,本身的数字就是价值
        hm.put("J", 11);
        hm.put("Q", 12);
        hm.put("K", 13);
        hm.put("A", 14);
        hm.put("2", 15);
        hm.put("B_Joker", 50);
        hm.put("R_Joker", 100);
    }

    public PokerGame() {

        // 洗牌
        Collections.shuffle(list);

        // 发牌
        ArrayList<String> lord = new ArrayList<>();
        ArrayList<String> player1 = new ArrayList<>();
        ArrayList<String> player2 = new ArrayList<>();
        ArrayList<String> player3 = new ArrayList<>();

        // 遍历牌盒得到每一张牌
        for (int i = 0; i < list.size(); i++) {
            String poker = list.get(i);
            if(i <= 2){
                lord.add(poker);
                continue;
            }

            // 给三个玩家轮流发牌
            // i % 3 == 0   player1
            // i % 3 == 1   player2
            // i % 3 == 2   player3
            if(i % 3 == 0){
                player1.add(poker);
            }else if(i % 3 == 1){
                player2.add(poker);
            }else{
                player3.add(poker);
            }
        }

        // 排序
        order(lord);
        order(player1);
        order(player3);
        order(player3);

        // 看牌
        lookPoker("底牌", lord);
        lookPoker("玩家1", player1);
        lookPoker("玩家2", player2);
        lookPoker("玩家3", player3);

    }

    // 利用牌的价值进行排序
    // 参数:集合
    public void order(ArrayList<String> list){
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                // 1. 计算 o1 的花色和价值
                String color1 = o1.substring(0, 1);
                int value1 = getValue(o1);

                // 2. 获取 o2 的花色和价值
                String color2 = o2.substring(0, 1);
                int value2 = getValue(o2);

                // 3. 比较 o1 和 o2 的价值
                int i = value1 - value2;
                return i == 0 ? color1.compareTo(color2) : i;
            }
        });
    }

    // 计算牌的价值
    // 参数:牌
    // 返回值:价值
    public int getValue(String poker){
        // 先判断是不是大小王
        if(poker.equals("B_Joker") || poker.equals("R_Joker")){
            return hm.get(poker);
        }
        // 再截取
        // 获取牌上的数字
        String number = poker.substring(1);
        // 拿着数字到 map 集合中判断是否存在
        // 存在,获取价值
        // 不存在,类型转换
        if(hm.containsKey(number)){
            return hm.get(number);
        }else{
            return Integer.parseInt(number);
        }
    }

    /**
     *
     * @param name 玩家名称
     * @param list 玩家牌组
     */
    public void lookPoker(String name, ArrayList<String> list){
        System.out.print(name + ": ");
        for (String poker : list) {
            System.out.print(poker + " ");
        }
        System.out.println();
    }

}

不可变集合

不可变集合:不可以被修改的集合

创建不可变集合的应用场景

  • 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
  • 当集合对象被不可信的库调用时,不可变形式是安全的。

创建不可变集合的书写格式

在 List、Set、Map 接口中,都存在静态的 of 方法,可以获取一个不可变的集合。

方法名称 说明
static List of(E...elements) 创建一个具有指定元素的 List 集合对象
static Set of(E...elements) 创建一个具有指定元素的 Set 集合对象
static <K, V> Map<K, V> of(E...elements) 创建一个具有指定元素的 Map 集合对象

注意:这个集合不能添加,不能删除,不能修改。

package com.faxont.immutable;

import java.util.List;

public class ImmutableDemo1 {
    public static void main(String[] args) {
        /*
            创建不可变的 List 集合
            "张三", "李四", "王五", "赵六"
         */

        // 一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        List<String> list = List.of("张三", "李四", "王五", "赵六");

        list.forEach(System.out::println);
    }
}
package com.faxont.immutable;

import java.util.List;
import java.util.Set;

public class ImmutableDemo2 {
    public static void main(String[] args) {
        /*
            创建不可变的 Set 集合
            "张三", "李四", "王五", "赵六"

            细节:
                当我们要获取一个不可变的 Set 集合时,里面的参数一定要保证唯一性
         */

        // 一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
          Set<String> set = Set.of("张三", "李四", "王五", "赵六");
//        Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");

        set.forEach(System.out::println);
    }
}
package com.faxont.immutable;

import java.util.Map;
import java.util.Set;

public class ImmutableDemo3 {
    public static void main(String[] args) {
        /*
            创建不可变的 Map 集合
            "张三", "南京", "李四", "北京", "王五", "上海", "赵六",
             "广州", "孙七", "深圳", "周八", "杭州", "吴九", "宁波",
              "郑十", "苏州", "刘一", "无锡", "陈二", "嘉兴"

            细节1:
                键是不能重复的
            细节2:
                Map 里面的 of 方法,参数是有上限的,最多只能传递20个参数,10个键值对
            细节3:
                如果我们要传递多个键值对对象,数量大于10个,在 Map 接口中还有一个方法:ofEntries
         */

        // 一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        Map<String, String> map = Map.of("张三", "南京", "李四", "北京", "王五", "上海", "赵六",
                "广州", "孙七", "深圳", "周八", "杭州", "吴九", "宁波",
                "郑十", "苏州", "刘一", "无锡", "陈二", "嘉兴");

        Set<String> keys = map.keySet();
        for (String key : keys) {
            System.out.println(key + "=" + map.get(key));
        }

        System.out.println("-----------------------------");

        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}
package com.faxont.immutable;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class ImmutableDemo4 {
    public static void main(String[] args) {
        /*
            如果我们要传递多个键值对对象,数量大于10个,在 Map 接口中还有一个方法:ofEntries
         */

        // 1. 创建一个普通的 Map 集合
        HashMap<String, String> hm = new HashMap<>();
        hm.put("张三", "南京");
        hm.put("李四", "北京");
        hm.put("王五", "上海");
        hm.put("赵六", "广州");
        hm.put("孙七", "深圳");
        hm.put("周八", "杭州");
        hm.put("吴九", "宁波");
        hm.put("郑十", "苏州");
        hm.put("刘一", "无锡");
        hm.put("陈二", "嘉兴");
        hm.put("aaa","111");

        // 2. 利用上面的数据来获取一个不可变的集合

//        // 获取到所有的键值对对象(Entry 对象)
//        Set<Map.Entry<String, String>> entries = hm.entrySet();
//        // 把 entries 变成一个数组
//        // toArray 方法在底层会比较集合的长度跟数组的长度两者大小
//        // 如果集合的长度 > 数组的长度,数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
//        // 如果集合的长度 <= 数组的长度,数据在数组中放得下,此时不会创建新的数组,而是直接用,多余部分用 null 补全
//        Map.Entry<String, String>[] entryArray = entries.toArray(new Map.Entry[0]);
//
//        // 不可变的 map 集合
//        Map<String, String> map = Map.ofEntries(entryArray);
//        System.out.println(map);

//        Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));

        Map<String, String> map = Map.copyOf(hm);// JDK 10 开始
    }
}
posted @ 2026-04-14 22:23  弋湖  阅读(21)  评论(0)    收藏  举报