集合进阶

集合进阶

单列集合顶层接口

集合体系结构

  • 单列集合:在添加元素时,一次只能添加一个元素。
  • 双列集合:在添加元素时,可以一次添加一对数据。

单列集合

  • 单列集合最顶层的为Collection接口,该接口定义了集合的最基本操作。

Collection 接口可分为两种单列集合

  • list系列集合:ArrayListLinkedListVector
  • set系列集合:HashSet(LinkedHashSet)、TreeSet

list系列集合

  • 添加的元素是有序的、可重复的、有索引的

set系列集合

  • 添加的元素是无序的、不可重复的(可以进行数组去重)、无索引的(不能通过索引获取值)

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

方法名称描述
public boolean add(E e)添加元素
public void clear()清空集合中的所有元素
public boolean remove(Object o)把给定的对象在当前集合中删除
public boolean contains(Object o)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()获取当前集合中元素的个数

注意

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

package ArrayList;

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

public class CollectionText {
    public static void main(String[] args) {
        //目的:为了学习他的方法
        //自己在做一些练习的时候,还是按照之前的方式去创建对象
        Collection<String> c = new ArrayList<>();

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

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

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

        //4.判断元素是否包含

        boolean result = c.contains("hello");
        System.out.println(result);//false
        //细节: 底层是依靠Equals方法进行判断是否存在的
        //所以:如果集合中存储的是自定义对象,也想通过contains方法判断元素是否包含,那么在javaBean中,一定要重写equals方法
        Collection<Student> students = new ArrayList<>();
        students.add(new Student("张三", 18));
        students.add(new Student("李四", 19));

        //姓名和年龄一样我们就认为是同一个人
        //javabean类中重写了equals方法后
        System.out.println(students.contains(new Student("张三", 18)));//true
        System.out.println(students.contains(new Student("李四", 19)));//true

        //判断集合长度
        System.out.println(students.size());//2
    }
}

class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.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的遍历方式

  1. 迭代器遍历
  2. 增强for遍历
  3. lambda表达式遍历

迭代器遍历

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

Collection集合获取迭代器

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

Iterator迭代器常用方法

方法名称说 明
boolean hasNext()判断当前位置是否有元素,有元素返回true,否则返回false
E next()返回当前位置的元素,并将迭代器指向下一个位置
public class IteratorText {
    public static void main(String[] args) {
        //获取迭代器对象
        Iterator<Student> iterator = students.iterator();
        //迭代器就好比是一个箭头,默认指向集合的0索引处
        //利用循环获取集合中的每一个元素
        while (iterator.hasNext()) {
            //next方法的两件事情 获取元素并移动指针
            Student student = iterator.next();
            System.out.println(student.name + " " + student.age);

        }
        //当循环结束之后,迭代器的指针已经指向了最后没有元素的位置
        //  System.out.println(iterator.next());//java.util.NoSuchElementException: NoSuchElementException
        //迭代器遍历完毕后,指针不会复位
        //循环中只能使用一次next方法
        //在迭代器遍历时,不能用集合的方法进行增加或者删除
    }
}

注意

迭代器的四个细节

  1. 如果当前位置没有元素,还要强行获取,会报NoSuchElementException
  2. 如果迭代器遍历完毕,指针不会复位
  3. 循环中只能用一次next方法
  4. 迭代器遍历时,不能用集合的方法进行增加或者删除

增强for遍历

  • 增强for的底层就是迭代器,为了简化迭代器的代码书写的
  • 他是jdk5之后出现的,其内部原理就是一个iterator迭代器
  • 所有的单列集合和数组才能使用增强for进行遍历
public class ForEachText {
    public static void main(String[] args) {
        //增强for遍历
        Collection<String> c1 = new ArrayList<>();
        c1.add("hello");
        c1.add("world");
        //注意
        //s其实就是一个第三方变量,在循环的过程中依次表示集合中的每一个数据
        //增强for的快捷生成方式 集合名字 + for 回车
        for (String s : c1) {
            System.out.println(s);
        }

        //修改增强for中的变量,不会改变集合中原本的数据
        for (String s : c1) {
            s = "java";
        }
        //因为s是一个第三方变量,修改了s,不会影响集合中的数据
        System.out.println(c1);//[hello, world]
    }
}

lambda表达式遍历

得益于jdk8开始的新技术lambda表达式,提供了一种更简单、更直接的遍历的和的方式

方法名称描 述
default void forEach(Consumer<? super T> action):结合lambda遍历集合
public class LambdaText {
    public static void main(String[] args) {
        Collection<String> c2 = new ArrayList<>();
        c2.add("hello");
        c2.add("world");
        //利用匿名内部类的方式进行遍历
        //方法的底层也会自己遍历集合,依次得到集合中的每一个元素
        //把得到的每一个元素,传递给下面的accecp方法
        //s依次表示集合中的每一个数据
        c2.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s + "匿名内部类");
            }
        });
        //利用lambda表达式进行遍历
        c2.forEach(s -> System.out.println(s + "lambda表达式"));
    }
}

三种遍历方式

  • 迭代器:在遍历过程中需要删除元素,请使用迭代器
  • 增强forlambda:如果在遍历过程中仅仅需要遍历,那么使用增强for或lambda即可

list集合

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

list集合特有的方法

  • Collection的方法List都继承了
  • List集合因为有索引,所以多了很多索引操作的方法
方法名称说明
void add (int index, E element)在指定位置插入元素
E remove(int index)删除指定索引处的元素,返回被删除的元素
E get(int index)返回指定索引处的元素
E set(int index,E element)修改指定索引处的元素,返回被修改的元素
public class ListText {
    public static void main(String[] args) {
        //list系列集合
        List<String> list = new ArrayList<>();

        //1.添加元素
        list.add("hello");
        list.add("world");

        //在指定位置添加元素
        //细节:原来索引上的元素会依次往后移
        list.add(1, "java");

        //2.删除指定索引位置的元素 返回被删除的元素
        //细节:删除时优先删除实参和形参类型一致的那个方法
        list.remove(1);
        System.out.println(list);//[hello, java]

        //3.修改指定索引位置的元素 返回被修改的元素
        list.set(1, "修改");
        System.out.println(list);//[hello, 修改]

        //4.返回指定索引位置的元素
        System.out.println(list.get(1));//修改
    }
}

list集合的遍历

public class ListText {
    public static void main(String[] args) {
        //list集合的遍历

        List<String> list1 = new ArrayList<>();
        list1.add("hello");
        list1.add("world");
        list1.add("java");

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

        //2.列表迭代器
        ListIterator<String> listIterator = list1.listIterator();
        while (listIterator.hasNext()) {
            String s = listIterator.next();
            System.out.println(s);
        }

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

        //4.lambda表达式
        list1.forEach(s -> System.out.println(s));

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

五种遍历方式对比

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

linkList集合

  • 底层数据结构是双链表,查询慢、增删快,但是如果操作的是首尾元素,速度也是极快的
  • linkLIst本身多了很多直接操作首尾元素特有的API
特有方法说明
public void addFirst(E e)在链表头部添加元素
public void addLast(E e)在链表尾部添加元素
public E getFirst()获取链表第一个元素
public E getLast()获取链表最后一个元素
public E removeFirst()删除链表第一个元素
public E removeLast()删除链表最后一个元素

数据结构的特点和作用

  • 栈:后进先出,先进后出
  • 队列:先进先出,后进后出
  • 数组:内存连续区域,查询快,增删慢
  • 链表:元素是游离的,查询慢,首位操作极快

泛型深入

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

注意

泛型中只能写引用数据类型

没有泛型的时候 集合如何存储数据

public class GenericText {
    public static void main(String[] args) {
        //泛型
        //没有泛型的时候,集合如何存储数据
        //如果我们没有给集合指定类型,默认会认为所有的子类类型都是Object
        //此时可以往集合中添加任意的数据类型
        //带来一个坏处 我们在获取数据的时候 无法使用他的特有行为
        //创建集合对象
        List list2 = new ArrayList();

        //添加数据
        list2.add("hello");
        list2.add(123);
        list2.add(new Student("张三", 18));

        //遍历集合中的每一个元素
        for (Object o : list2) {

            System.out.println(o);
        }
    }
}

泛型的细节

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

泛型类

  • 当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
    格式:修饰符 class 类名<泛型变量>{}
  • 泛型变量可以理解为变量,但是不是用来记录数据的,而是用来记录类型的
package ArrayList;

import java.util.Arrays;

//泛型类
public class MyArrayList<E> {
    //创建一个长度为10的数组
    Object[] data = new Object[10];
    int size;

    public Boolean add(E e) {
        data[size++] = e;
        return true;
    }

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

    @Override
    public String toString() {
        return Arrays.toString(data);
    }
}

泛型类使用

public class GenericText {
    public static void main(String[] args) {
        //泛型类
        MyArrayList<String> myArrayList = new MyArrayList<>();
        myArrayList.add("hello");
        System.out.println(myArrayList.toString());
    }
}

泛型方法的使用

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

  • 使用类名后面定义的泛型(所有方法都能用)
  • 在方法申明上定义自己的泛型(只有本方法能用)
package ArrayList;

public class MyArrayList {
    public <E> boolean add(E e) {
        data[size++] = e;
        return true;
    }
}

泛型接口

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

 //泛型接口的两种使用方式
//1.实现类给出具体的类型
MyArrayList2<String> myArrayList2 = new MyArrayList2<>();
        myArrayList2.

add("hello");
//myArrayList2.add(123);
        System.out.

println(myArrayList2);

//2.实现类延续泛型 创建实现类对象时再确定类型
public static class MyArrayList2<E> implements List<E> {
    //...
}

泛型的继承和通配符

  • 泛型不具备继承性,但是数据具备继承性

  • 泛型里面写的是什么类型,那么只能传递什么类型的数据

  • 泛型方法有一个弊端 此时他可以接收任意的数据类型

  • 如果希望方法只能接收A、B、C类型,此时就可以使用泛型的通配符

  • ?表示不确定的类型

  • 也可以进行类型限定

  • ?extends E:表示可以传递E或者E所有的子类类型

  • ?super E:表示可以传递E或所有E的父类类型

应用场景

  • 如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口
  • 如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型通配符

泛型通配符

  • 关键点:可以限定类型的范围
 public class GenericText {
    public static void main(String[] args) {
        //要求1:该方法能养所有品种的猫,但不能养狗
        //猫类
        ArrayList<LHMCat> list3 = new ArrayList<>();
        ArrayList<PersianCat> list4 = new ArrayList<>();
        keepPet1(list3);
        keepPet1(list4);
        //狗类
        ArrayList<HSDog> list5 = new ArrayList<>();
        keepPet1(list5);//报错

        //要求2:该方法能养所有品种的狗,但不能养猫
        ArrayList<HSDog> list6 = new ArrayList<>();
        keepPet2(list6);
        ArrayList<LHMCat> list7 = new ArrayList<>();
        keepPet2(list7);//报错

        //要求3:该方法能养所有的动物,但是不能传递其他类型
        ArrayList<Student> list8 = new ArrayList<>();
        keepPet3(list8);//报错
        ArrayList<Cat> list9 = new ArrayList<>();
        keepPet3(list9);
    }
}

class Animal {
    String name;
    int age;

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

    public void eat() {
        System.out.println("动物在吃东西");
    }

}

//猫类
abstract class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    //方法重写
    public abstract void eat();
}

//波斯猫类
class PersianCat extends Cat {

    public PersianCat(String name, int age) {
        super(name, age);
    }

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

//狸花猫类
class LHMCat extends Cat {

    public LHMCat(String name, int age) {
        super(name, age);
    }

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


//狗类
abstract class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }

    //方法重写
    public abstract void eat();
}

//泰迪类
class TeddyDog extends Dog {

    public TeddyDog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("我是一只叫做" + this.name + "的泰迪,正在吃泰迪狗粮");
    }
}

//哈士奇类
class HSDog extends Dog {

    public HSDog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("我是一只叫做" + this.name + "的哈士奇,正在拆家");
    }
}

数据结构(树)

  • 度:每一个节点的子节点数量(二叉树中,任意节点的度<=2)
  • 树高:树的总层数
  • 根节点:树的总层数
  • 根节点:最顶层的节点
  • 左子节点:左下方的节点
  • 右子节点:右下方的节点

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

特点:

  • 每个节点上最多有两个子节点
  • 任意节点左子树上的值都小于当前节点
  • 任意节点右子树上的值都大于当前节点
  • 添加节点规则:小的存左边,大的存右边,一样的不存

二叉查找树的遍历方式

  • 前序遍历:先访问根节点,再访问左子树,最后访问右子树(根左右
  • 中序遍历:先访问左子树,再访问根节点,最后访问右子树(左根右
  • 后序遍历:先访问左子树,再访问右子树,最后访问根节点(左右根
  • 层序遍历:按层遍历,从上到下,从左到右

二叉查找树的弊端

  • 添加元素时有可能会出现左右节点不平衡的情况

平衡二叉树

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

平衡二叉树的旋转机制

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

平衡二叉树旋转机制

  • 确定支点:从添加的节点开始,不断的往父节点找不平衡的节点 步骤
  • 以不平衡的点作为支点
  • 把支点左旋降级,变成左子节点
  • 晋升原来的右子节点

双列集合

特点:

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

双列集合map

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

方法

注意

  • 对于不可变的双列集合,可以使用Map.of(key1,value1,key2,value2,...)创建
  • 使用Map.of(key1,value1,key2,value2,...)创建的集合,是无法进行修改的(添加或删除键值对元素)
方法名称描述
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 MapList;

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

public class MapList {

    public static void main(String[] args) {
        //双列集合
        //创建map集合对象
        Map<String, String> map = new HashMap<>();

        //添加元素
        //在添加数据的时候,如果键不存在,那么直接把键值添加到map集合当中
        //在添加数据的时候,如果键是存在的,那么会把原有键值对对象覆盖,会把覆盖的值进行返回
        map.put("张三", "男");
        map.put("李四", "女");
        map.put("王五", "男");

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

        //按照键删除元素
        String RemoveResult = map.remove("张三");
        //删除结果
        //返回删除的值
        System.out.println("删除结果" + RemoveResult);//删除结果男
        System.out.println(map);//{李四=女, 王五=男}

        //清空集合
        map.clear();
        System.out.println(map);//{}

        //判断是否包含(依据键)
        System.out.println(map.containsKey("李四"));//false

        //判断是否包含(依据值)
        System.out.println(map.containsValue("男"));//false

        //获取集合长度
        System.out.println(map.size());//0

    }
}

map的遍历方式

通过键获取值

public class MapList {
    public static void main(String[] args) {
        //map的遍历方式
        //创建集合对象
        Map<String, String> map2 = new HashMap<>();
        map2.put("张三", "男");
        map2.put("李四", "女");
        map2.put("王五", "男");

        //获取集合中所有的键
        Set<String> keySet = map2.keySet();
        System.out.println(keySet);//[李四, 张三, 王五]
        //循环获取键的值
        keySet.forEach(key -> System.out.println(map2.get(key)));
    }
}

通过键值对的形式

public class MapList {
    public static void main(String[] args) {
        //通过键值对的形式遍历双列集合
        Map<String, String> map3 = new HashMap<>();
        map3.put("zhangsan", "男");
        map3.put("lisi", "女");
        map3.put("wangwu", "男");
        //通过双列集合的键值对对象进行遍历
        //通过entrySet方法获取所有的键值对对象,并返回一个set集合
        Set<Map.Entry<String, String>> entries = map3.entrySet();
        System.out.println(entries);//[lisi=女, zhangsan=男, wangwu=男]
        //遍历set集合获取每一个键值对对象
        entries.forEach(entry -> System.out.println(entry.getKey() + ":" + entry.getValue()));

    }
}

利用lambda表达式进行遍历

方法名称描述
default void forEach(BiConsumer<? super K, ? super V> action)遍历集合,并对集合中的每一个元素进行操作
public class MapList {
    public static void main(String[] args) {
        //利用`lambda`表达式进行遍历
        Map<String, String> map4 = new HashMap<>();
        map4.put("zhangsan123", "男");
        map4.put("lisi123", "女");
        map4.put("wangwu123", "男");
        map4.forEach((key, value) -> System.out.println(key + ":" + value));
    }
}

HashMap的特点

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

练习

public class MapList {
    public static void main(String[] args) {
        //需求:创建一个HashMap集合,键是学生对象,值是籍贯
        //存储三个键值对元素,并遍历
        //要求:同姓名,同年龄 认为是同一个学生
        //核心点 HashMap的键位置如果存储的是自定义对象 那么需要重写hashCode和equals方法
        //创建存储学生对象的集合
        Map<Student, String> map5 = new HashMap<>();
        map5.put(new Student("张三", 18), "北京");
        map5.put(new Student("李四", 19), "上海");
        map5.put(new Student("张三", 18), "广州");

        //遍历集合
        map5.forEach((student, address) -> System.out.println(student.toString() + ":" + address));


        //需求:统计投票人数
        //某班级80名学生现在需要组成秋游活动,班长提供了四个景点分别是(A、B、C、D)每个学生只能选择一个景点,请统计出最终那个景点去的人多
        Map<String, String> map6 = new HashMap<>();
        String[] placeList = {"A", "B", "C", "D"};
        for (int i = 0; i < 80; i++) {
            //随机获取景点
            String place = placeList[(int) (Math.random() * placeList.length)];
            map6.put(Objects.toString(i), place);
        }
        Map<String, Integer> map7 = new HashMap<>();
        map6.forEach((key, value) -> {
            if (map7.containsKey(value)) map7.put(value, map7.get(value) + 1);
            else map7.put(value, 1);
        });
        String finalPlace = "";
        int max = 0;
        Set<Map.Entry<String, Integer>> entrySet = map7.entrySet();
        for (Map.Entry<String, Integer> entry : entrySet) {
            if (entry.getValue() > max) {
                finalPlace = entry.getKey();
                max = entry.getValue();
            }
        }
        ;

        System.out.println(map7);
        System.out.println("最终景点:" + finalPlace + ",人数:" + max);//最终景点:B,人数:25
    }
}

LinkHashMap

特点

  • 由键决定:有序,不重复,无索引
  • 这里的有序指的是:保证存储和取出的元素顺序一致
public class MapList {
    public static void main(String[] args) {
        //linkHashMap可以保证元素在存和取的时候顺序一致
        Map<String, Integer> map8 = new LinkedHashMap<>();
        map8.put("lisi", 19);
        map8.put("zhangsan", 18);
        map8.put("wangwu", 20);
        System.out.println(map8);//{lisi=19, zhangsan=18, wangwu=20}

        Map<String, Integer> map9 = new HashMap<>();
        map9.put("zhangsan", 18);
        map9.put("lisi", 19);
        map9.put("wangwu", 20);
        System.out.println(map9);//{lisi=19, zhangsan=18, wangwu=20}
    }
}

TreeMap

特点

  • TreeMapTreeSet一样,都是红黑树结构
  • 由键决定的特性:不重复、无索引、可排序
  • 可排序:对键进行排序,默认是升序,可以指定排序规则

代码书写的两种排序规则

  1. 实现Comparable接口,重写compareTo()方法
  2. 创建集合时传递Comparator比较器对象,指定比较规则(优先)
public class MapList {
    public static void main(String[] args) {
        //需求1
        //键:整数表示id
        //值:字符串表示商品名称
        //要求:按照id升序排序,如果id相同,按照商品名称升序排序
        Map<Integer, String> map10 = new TreeMap<>(new Comparator<Integer>() {
            //实现接口降序排序
            @Override
            public int compare(Integer o1, Integer o2) {
                //o1:当前要添加的元素
                //o2:当前已经在红黑树中存在的元素
                return o2 - o1;
            }
        });
        map10.put(1, "苹果");
        map10.put(2, "香蕉");
        map10.put(3, "橘子");
        System.out.println(map10);


        //需求
        //键:学生对象
        //值:籍贯
        //要求:按照学生年龄升序排列,年龄一样按照姓名字母排列,同姓名年龄视为同一个人
        Map<Student, String> map11 = new TreeMap<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                //返回值
                //负数:表示当前要当前要添加的元素是小的,存左边
                //正数:表示当前要当前要添加的元素是大的,存右边
                //0: 表示当前要当前要添加的元素和已经存在的元素一样,不存


                //年龄不同 按照年龄升序排列
                if (o1.getAge() != o2.getAge()) return o1.getAge() - o2.getAge();
                else {
                    //姓名相同 舍弃 视为同一个人
//                    if (o1.getName().equals(o2.getName())) return 0;
                    //姓名不同 按照姓名首字母排列
                    //compareTo方法 它是先比较对应字符的大小(ASCII码顺序)
//                    如果参数字符串等于此字符串,则返回值 0;
//                    如果此字符串小于字符串参数,则返回一个小于 0 的值;
//                    如果此字符串大于字符串参数,则返回一个大于 0 的值。
                    System.out.println(o1.getName().compareTo(o2.getName()));
                    return o1.getName().compareTo(o2.getName());
                }
            }
        });
        map11.put(new Student("张三", 19), "香蕉");
        map11.put(new Student("张三", 19), "苹果");
        map11.put(new Student("王五", 20), "橘子");
        System.out.println(map11);


        //需求:字符串“aababcabcdabcde”请统计字符串中每个字符出现的个数
        String str = "bbaababcabcdabcde";
        char[] chars = str.toCharArray();
        Map<String, Integer> map12 = new TreeMap<>();
        for (Character aChar : chars) {
            String aCharStr = aChar.toString();
            if (map12.containsKey(aCharStr)) map12.put(aCharStr, map12.get(aCharStr) + 1);
            else map12.put(aCharStr, 1);
        }
        System.out.println(map12);
        StringBuilder finalStr = new StringBuilder();
        for (Map.Entry<String, Integer> entry : map12.entrySet()) {
            finalStr.append(entry.getKey()).append(":").append(entry.getValue()).append(" ");
        }
        System.out.println(finalStr);
    }
}

可变参数

  • 方法形参的个数是可以发生变化的
  • 格式...名字
  • 可变参数的底层就是一个数组,只不过不需要我们创建了,java会帮我们创建好
public class VarArgs {
    public static void main(String[] args) {
        System.out.println(getSun(1, 2, 3, 4));
    }

    public static int getSun(int... args) {
        System.out.println(Arrays.toString(args));//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        return Arrays.stream(args).sum();
    }
}

细节

  1. 在方法的形参中,最多只能写一个可变参数
  2. 如果方法除了可变参数外,还有其他的参数,那么可变参数要写在最后

不可变集合

  • 不可变集合就是不可修改该集合的元素,及长度不可变,元素不可变,相当于常量

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

listSetMap接口中,都存在静态的of方法,可以获取一个不可变的集合

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

注意

这个集合不能添加,不能删除,不能修改

package ArrayList;

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

public class StaticList {
    public static void main(String[] args) {
        //不可变集合
        //该集合一旦创建完毕之后不可以修改
        List<Integer> list = List.of(1, 2, 3);
        System.out.println(list);
        list.forEach(System.out::println);

        //修改 报错
        //list.set(1, 4);
        //list.add(4);
        System.out.println(list);

        Set<Integer> set = Set.of(41, 42);
        System.out.println(set);

        //map中的可变参数是有上限的 最多20个 也就是10个键值对
        Map<Integer, Integer> map = Map.of(1, 2, 3, 4);
        System.out.println(map);

        //超过10个元素 用ofEntries方法
        Map<Object, Object> objectObjectMap = Map.ofEntries(map1.entrySet().toArray(new Map.Entry[0]));
        System.out.println(objectObjectMap);
    }
}

注意

  • set中的元素不可重复,如传递的是引用数据类型,此时比较的是地址值
  • set中如果传递引用类型时需重写 equals方法 才能对引用类型的值进行比较
public class StaticList {
    public static void main(String[] args) {
        Set<StaticStudent> students = Set.of(new StaticStudent("张三", 20), new StaticStudent("张三", 20));//报错
        System.out.println(students);
    }

    class StaticStudent {
        String name;
        int age;

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

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            StaticStudent that = (StaticStudent) o;
            return age == that.age && Objects.equals(name, that.name);
        }
    }
}
上次更新 2025/10/26 17:25:00