Java 集合与函数式编程知识整理


1. 函数式接口

1.1 概念

有且仅有一个抽象方法的接口,称为函数式接口。
可使用 @FunctionalInterface 注解进行检测。

1.2 常用函数式接口

接口抽象方法作用
Supplier<T>T get()供给型,生产数据
Consumer<T>void accept(T t)消费型,操作数据
Function<T, R>R apply(T t)转换型,类型转换
Predicate<T>boolean test(T t)断言型,判断
Supplier 示例
public static void main(String[] args) {
    method(() -> {
        int[] arr = {5, 34, 4, 5, 76, 7};
        Arrays.sort(arr);
        return arr[arr.length - 1];
    });
}

public static void method(Supplier<Integer> supplier) {
    System.out.println(supplier.get());
}

Consumer 示例

public static void main(String[] args) {
    method(s -> System.out.println(s.length()), "abcdefg");
}

public static void method(Consumer<String> consumer, String s) {
    consumer.accept(s);
}

Function 示例

public static void main(String[] args) {
    method(integer -> integer + "", 1000);
}

public static void method(Function<Integer, String> function, int a) {
    String s = function.apply(a);
    System.out.println(s + 1);   // 10001
}

Predicate 示例

public static void main(String[] args) {
    method(s -> s.length() == 7, "abcdefg");
}

public static void method(Predicate<String> predicate, String s) {
    boolean test = predicate.test(s);
    System.out.println(test);
}
  1. Stream 流
    Stream 是 Java 8 引入的"流水线"式处理数据的工具,与 IO 流无关,主要用于对集合或数组进行过滤、映射、归约等操作。

2.1 获取 Stream
数组:Stream.of(T... values)

单列集合:调用 stream() 方法(Collection 接口提供)

// 数组
Stream<String> stream1 = Stream.of("a", "b", "c");

// 集合
ArrayList<String> list = new ArrayList<>();
Stream<String> stream2 = list.stream();

2.2 常用方法
方法 描述 类型
forEach(Consumer) 逐一处理(遍历) 终结方法
count() 统计元素个数 终结方法
filter(Predicate) 过滤,返回新流 中间方法
limit(long n) 取前 n 个元素 中间方法
skip(long n) 跳过前 n 个元素 中间方法
concat(Stream a, Stream b) 合并两个流(静态) 中间方法
map(Function) 类型转换,返回新流 中间方法
distinct() 去重(依赖 hashCode/equals) 中间方法
collect(Collectors.toList()) 将流转为 List 集合 终结方法

Stream<String> stream = Stream.of("熊出没", "数码宝贝", "神厨小福贵");
List<String> result = stream
        .filter(s -> s.length() > 3)
        .collect(Collectors.toList());
System.out.println(result);  // [数码宝贝, 神厨小福贵]

示例:map 类型转换

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.map(integer -> integer + "")
      .forEach(s -> System.out.println(s + 1));
// 输出:11 21 31 41 51

2.3 综合练习
两个队伍名单处理:

  1. 队伍一保留名字为 3 个字的成员,取前 3 人;

  2. 队伍二保留姓张的成员,跳过前 2 人;

  3. 合并两队并打印。

ArrayList<String> one = new ArrayList<>(Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公"));
ArrayList<String> two = new ArrayList<>(Arrays.asList("古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三"));

Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);

Stream.concat(streamOne, streamTwo).forEach(System.out::println);
  1. 方法引用
    方法引用是 Lambda 表达式的进一步简化,要求被引用方法的参数和返回值与重写方法一致。

3.1 引用规则

  1. 对象名引用成员方法:对象::成员方法

  2. 类名引用静态方法:类名::静态方法

  3. 构造方法引用:类名::new

  4. 数组引用:类型[]::new

示例:对象名引用成员方法

// Lambda 写法
method(() -> " abcdefg ".trim());
// 方法引用
method(" abcdefg "::trim);

public static void method(Supplier<String> supplier) {
    System.out.println(supplier.get());
}

示例:类名引用静态方法

method(Math::random);  // Supplier<Double> 引用 Math.random()

示例:构造方法引用

// Function<String, Person>
method(Person::new, "张三");

public static void method(Function<String, Person> function, String name) {
    Person person = function.apply(name);
    System.out.println(person);
}

示例:数组引用

// Function<Integer, int[]>
method(int[]::new, 10);

public static void method(Function<Integer, int[]> function, int len) {
    int[] arr = function.apply(len);
    System.out.println(arr.length);
}
  1. 集合框架概述
    Java 集合框架提供了一组接口和类,用于存储和操作对象。

  2. 单列集合:每个位置只存储一个元素(Collection)

  3. 双列集合:每个元素由键值对组成(Map)

  4. 集合与数组的区别:
    集合长度可变,数组定长
    集合只能存储引用类型(基本类型会自动装箱)
    集合提供丰富的方法(add、remove、contains 等)

Collection 接口
Collection 是单列集合的根接口,其子接口包括 List 和 Set。

5.1 常用方法
方法 描述
boolean add(E e) 添加元素
boolean addAll(Collection c) 添加另一个集合的所有元素
void clear()清空集合
boolean contains(Object o) 是否包含指定元素
boolean isEmpty() 集合是否为空
boolean remove(Object o) 移除指定元素
int size() 返回元素个数
Object[] toArray() 转换为 Object 数组
5.2 使用示例

Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.addAll(Arrays.asList("王五", "赵六"));
System.out.println(collection.size());        // 4
System.out.println(collection.contains("张三")); // true
collection.remove("王五");
for (String s : collection) {
    System.out.println(s);
}
  1. 迭代器 Iterator
    6.1 基本使用
    通过 iterator() 方法获取迭代器,用于遍历集合。

     `hasNext()`:判断是否存在下一个元素
    
     `next()`:返回下一个元素
    
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

注意:迭代次数不能超过元素个数,否则会抛出 NoSuchElementException。

6.2 并发修改异常
使用迭代器遍历集合时,若直接通过集合对象的 add/remove 方法修改集合,会引发 ConcurrentModificationException。

错误示例

ArrayList<String> list = new ArrayList<>(Arrays.asList("唐僧", "孙悟空", "猪八戒", "沙僧"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if ("猪八戒".equals(it.next())) {
        list.add("白龙马");   // 错误:使用集合自身的 add
    }
}

正确做法:使用 ListIterator (List 专有)提供的 add/remove 方法。

ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
    if ("猪八戒".equals(lit.next())) {
        lit.add("白龙马");    // 通过迭代器自身操作
    }
}

6.3 实现原理
不同集合的 iterator() 返回不同的内部类实现。例如 ArrayList 返回一个名为 Itr 的内部类,其通过 modCount 与 expectedModCount 的比较来检测并发修改。

  1. 数据结构基础
    7.1 常见数据结构

    :先进后出(LIFO),像手枪弹夹。

    队列:先进先出(FIFO),像排队。

    数组:查询快(通过索引),增删慢(需要移动元素)。

    链表:查询慢(需要遍历),增删快(只需修改指针)。

    单向链表:节点包含数据域和指向下一节点的指针。

    双向链表:节点包含数据域、指向前一节点和后一节点的指针。

  2. List 接口及其实现类
    8.1 List 接口
    特点:元素有序、有索引、可重复。
    常用实现类:ArrayList、LinkedList、Vector

通用方法:

方法 描述
void add(int index, E element) 指定位置插入
E remove(int index) 删除指定索引元素并返回
E set(int index, E element) 修改指定索引元素
E get(int index) 获取指定索引元素
int size() 元素个数
遍历方式

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

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

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

8.2 ArrayList
数据结构:数组

特点:查询快,增删慢;线程不安全。

扩容机制

  1. 无参构造:首次添加元素时,数组初始化为长度 10;后续扩容为原容量的 1.5 倍。

  2. 有参构造:可指定初始容量。

ArrayList<Integer> list = new ArrayList<>();
// 删除时注意:直接传 int 会被当作索引,需装箱
list.remove(Integer.valueOf(2));

8.3 LinkedList
数据结构:双向链表

特点:查询慢,增删快;无索引;线程不安全。

特有方法(主要操作首尾):

方法 描述
addFirst(E e) 头部添加
addLast(E e) 尾部添加
getFirst() 获取第一个元素
getLast()获取最后一个元素
removeFirst() 移除并返回第一个元素
removeLast() 移除并返回最后一个元素
push(E e) 压栈(头部添加)
pop() 弹栈(头部移除并返回)

LinkedList<String> list = new LinkedList<>();
list.addLast("C");
list.addFirst("A");
System.out.println(list);    // [A, C]
list.push("First");
System.out.println(list.pop()); // First

8.4 Vector(了解)
数据结构:数组

特点:线程安全,其余与 ArrayList 类似。

扩容:无参构造默认容量 10,扩容为 2 倍;有参构造可按指定增量扩容。

8.5 增强 for 循环
格式:for (元素类型 变量名 : 数组或集合)
快捷键:集合名.for

底层原理:

数组:编译后为普通 for 循环

集合:编译后为迭代器,因此遍历时不可通过集合对象修改长度。

  1. Collections 工具类
    说明:操作集合的工具类,构造方法私有,所有方法为静态。

常用方法:

方法 描述
static <T> boolean addAll(Collection<? super T> c, T... elements) 批量添加元素
static void shuffle(List<?> list) 随机打乱顺序
static <T> void sort(List<T> list) 按自然顺序排序(元素实现 Comparable)
static <T> void sort(List<T> list, Comparator<? super T> c) 按指定比较器排序

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "小猫", "小狗", "小羊");
Collections.shuffle(list);
Collections.sort(list);
// 自定义对象排序
Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge());

9.1 Comparable 与 Comparator
Comparable:定义类的默认排序规则,实现 compareTo() 方法;

Comparator:外部比较器,可在不修改类的情况下自定义多种排序规则。

// 实现 Comparable 接口
public class Student implements Comparable<Student> {
    private int score;
    public int compareTo(Student o) {
        return this.score - o.score;
    }
}
// 外部 Comparator
Collections.sort(list, Comparator.comparingInt(Student::getScore));

Arrays.asList(T... a) 返回固定大小的 List,不可修改长度。

  1. Set 接口及其实现类
    特点:元素不可重复,无索引。

    所有 Set 底层均依赖 Map 实现。

10.1 HashSet
特点:无序、无索引、元素唯一、线程不安全。

数据结构:哈希表(数组 + 链表 + 红黑树)

HashSet<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple");
System.out.println(set);   // [banana, apple]

10.2 LinkedHashSet
特点:有序(保持插入顺序)、无索引、元素唯一、线程不安全。
数据结构:哈希表 + 双向链表

LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("C");
set.add("A");
set.add("B");
System.out.println(set);   // [C, A, B]

10.3 TreeSet
特点:可排序、无索引、元素唯一、线程不安全。

数据结构:红黑树

自然排序:TreeSet() 按元素的自然顺序

定制排序:TreeSet(Comparator)

TreeSet<Person> set = new TreeSet<>((o1, o2) -> o1.getAge() - o2.getAge());
set.add(new Person("张三", 18));
set.add(new Person("李四", 15));

10.4 元素唯一性原理
先比较 hashCode()

哈希不同 → 直接存储;

哈希相同 → 比较 equals();

equals 返回 true → 视为重复,不存储;

equals 返回 false → 通过链表/红黑树存储。

结论:自定义对象必须同时重写 hashCode() 和 equals() 方法。

  1. 泛型
    11.1 为什么使用泛型
    将运行时 ClassCastException 提前到编译期;

使代码更通用、更安全。

11.2 含有泛型的类

public class MyArrayList<E> {
    private Object[] arr = new Object[10];
    public void add(E e) { ... }
    public E get(int index) { ... }
}
// 使用
MyArrayList<String> list = new MyArrayList<>();

11.3 含有泛型的方法

public static <E> void print(E e) {
    System.out.println(e);
}

11.4 含有泛型的接口

public interface MyList<E> {
    void add(E e);
}
// 方式1:实现时确认类型
public class MyStringList implements MyList<String> { }
// 方式2:继续泛型
public class MyArrayList<E> implements MyList<E> { }

11.5 泛型通配符 ?

public void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

11.6 上下限
? extends 类:接收该类及其子类(上限)

? super 类:接收该类及其父类(下限)
public void method(List<? extends Number> list) { }  // Number 及子类
public void method(List<? super Number> list) { }    // Number 及父类
  1. Map 集合
    双列集合,每个元素由键(Key)和值(Value)组成,键不可重复。

12.1 HashMap
特点:无序、无索引、键唯一、值可重复、允许 null 键和 null 值、线程不安全。

数据结构:哈希表

常用方法
方法 描述
V put(K key, V value) 添加/覆盖,返回旧值
V remove(Object key) 根据键删除,返回被删值
V get(Object key) 根据键获取值
boolean containsKey(Object key) 是否包含指定键
Set<K> keySet() 获取所有键的 Set 集合
Set<Map.Entry<K,V>> entrySet()获取所有键值对
Collection<V> values() 获取所有值的 Collection
遍历方式

HashMap<String, String> map = new HashMap<>();
map.put("1", "苹果");
map.put("2", "香蕉");

// 方式1:keySet + get
for (String key : map.keySet()) {
    System.out.println(key + " = " + map.get(key));
}

// 方式2:entrySet
for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " = " + entry.getValue());
}

12.2 LinkedHashMap
特点:有序(插入顺序)、其余同 HashMap。

数据结构:哈希表 + 双向链表

12.3 TreeMap
特点:可以对键排序、键唯一、值可重复、键不能为 null。

数据结构:红黑树

排序规则:自然排序或构造函数传入 Comparator。

TreeMap<Person, String> map = new TreeMap<>((o1, o2) -> o2.getAge() - o1.getAge());
map.put(new Person("张三", 18), "北京");

12.4 Hashtable(了解)
特点:线程安全、不允许 null 键和值,其余类似 HashMap。

功能HashMapHashtable
线程安全
null 键/值允许不允许
效率

12.5 Properties
说明:Hashtable 的子类,键和值都必须是 String 类型。

主要用途:读取配置文件(.properties)

示例:读取 resources/config.properties
文件内容:

properties
username=root
password=123456

Java 代码:

Properties props = new Properties();
InputStream in = Demo.class.getClassLoader().getResourceAsStream("config.properties");
props.load(in);
System.out.println(props.getProperty("username"));  // root
  1. 集合嵌套
    13.1 List 嵌套 List
List<String> list1 = Arrays.asList("张三", "李四");
List<String> list2 = Arrays.asList("王五", "赵六");
List<List<String>> bigList = new ArrayList<>();
bigList.add(list1); bigList.add(list2);

13.2 List 嵌套 Map

Map<String, String> map1 = new HashMap<>();
map1.put("1", "小明");
List<Map<String, String>> list = new ArrayList<>();
list.add(map1);

13.3 Map 嵌套 Map

// 结构:课程名 -> (学号 -> 姓名)
Map<String, Map<Integer, String>> school = new HashMap<>();
Map<Integer, String> javaSE = new HashMap<>();
javaSE.put(1, "张三");
school.put("JavaSE", javaSE);
  1. 哈希表底层原理
    14.1 存储结构
    JDK 8 开始:数组 + 链表 + 红黑树

14.2 重要参数
默认初始容量:16

加载因子:0.75(达到容量的 75% 时扩容)

扩容:容量变为原来的 2 倍

树化阈值:链表长度 ≥ 8 且数组长度 ≥ 64 时,转红黑树

退化阈值:红黑树节点数 ≤ 6 时,转回链表

14.3 添加元素流程
根据 key 的 hashCode() 计算哈希值。

通过哈希值确定数组索引。

若该索引无元素,直接放入。

若冲突,通过 equals 判断 key 是否相同:

相同则覆盖 value;

不同则以链表或红黑树形式存储。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐