JAVA知识梳理:一文搞懂集合中的List与ArrayList的基础与进阶

大家好,我是加洛斯。是一名全站工程师👨‍💻,这里是我的知识笔记与分享,旨在把复杂的东西讲明白。如果发现有误🔍,万分欢迎你帮我指出来!废话不多说,正文开始 👇

本文详细的讲述了List中的特性,接着深入了List的实现类ArrayList的基本知识,涵盖构造方法、常用API、特性以及注意事项,并配有详细的代码讲解。

一、List基础知识

在Java中,List接口是Java集合框架中的一部分,提供了一组有序的可重复的元素集合 ,它定义在java.util包下。

1.1 JDK21以前

JDK21之前List接口继承自Collection接口,具有Collection接口的所有方法,同时还提供了一些额外的方法来处理有序集合中的元素,例如在其基础上添加了允许精确控制元素插入位置的操作。

java.util.Collection 接口是 Java 集合框架的根接口之一,它定义了一组通用的方法,用于操作集合中的元素。Collection 接口是 Java 集合框架中所有集合类的共同父接口。

1.3 JDK21及以后

JDK21以及之后,List接口继承自SequencedCollection接口(它的父接口是Collection),它为所有定义了明确元素顺序 的集合提供了一个统一、顶层的抽象。核心目的是为了解决 Java 集合框架长期存在的一个问题:对于所有有顺序的集合,缺乏一个统一的、标准的 API 来操作它们的第一个和最后一个元素,以及进行反向遍历。

二、List的特性

2.1 有序性

List会按照元素插入的顺序 来维护它们。当你遍历一个List时,元素的出现顺序与你添加它们的顺序完全一致,这意味着你可以按照特定的顺序获取、插入、删除或替换元素

java 复制代码
List<String> list = new ArrayList<>();
list.add("Apple");  // 第1个添加
list.add("Banana"); // 第2个添加
list.add("Cherry"); // 第3个添加
// 遍历结果永远是: Apple, Banana, Cherry
list.forEach(System.out::println);

2.2 可重复性

Set不同,List允许存储完全相同的元素。你可以多次向同一个List中添加同一个对象(或equals()方法返回true的不同对象)。

java 复制代码
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Apple"); // 完全允许,此时List中有两个"Apple"
System.out.println(list.size()); // 输出: 2

2.3 索引访问

这是List区别于其他Collection的显著特征。每个元素都有一个精确的索引位置,索引从0开始。你可以通过索引精确地定位、获取、修改或删除元素。

java 复制代码
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
String element = list.get(1);      // 获取: element = "B"
list.set(1, "X");                  // 修改: 列表变为 ["A", "X", "C"]
list.remove(0);                     // 删除: 列表变为 ["X", "C"]
list.add(1, "Y");                   // 插入: 列表变为 ["X", "Y", "C"]

2.4 允许null值

大多数List的实现(如ArrayListLinkedList)都允许存储null元素,甚至可以存储多个null

java 复制代码
List<String> list = new ArrayList<>();
list.add(null);
list.add("Apple");
list.add(null);
System.out.println(list); // 输出: [null, Apple, null]
  • 注意 :虽然允许,但在实际开发中建议谨慎使用null,因为它可能会导致遍历或处理时的NullPointerException。如果确实需要"空值"的概念,可以考虑使用Java 8Optional或自定义的空对象。

三、ArrayList

我们在创建一个 List 对象的时候,经常会写以下范式List list = new ArrayList();谁也没见过 List list = new List(); 这种写法吧,这究竟是为什么呢?

在 Java 中,List 是一个接口,而不是一个具体的类。接口本身不能直接实例化为对象,因为它只是一组方法的声明,没有实际的方法实现。所以我们要想实现List,就需要一个实现类。

ArrayListJava集合框架中的一个常用实现类,它的底层逻辑是通过数组实现的 ,它实现了 List 接口,提供了动态数组的功能。

java 复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

3.1 三种构造方法

ArrayList的底层是数组,所以它的初始容量并不是无限的,如果不指定的话,其初始容量为10。

java 复制代码
public class ArrayListCreation {
    public static void main(String[] args) {
        // 方式1:默认创建初始容量为10
        ArrayList<String> defaultList = new ArrayList<>();
        // 方式2:指定容量创建
        ArrayList<Integer> sizedList = new ArrayList<>(100);
        // 方式3:从数组转换
        List<String> fromArray = Arrays.asList("Java", "Python", "C++");
        ArrayList<String> arrayList = new ArrayList<>(fromArray);
        // Java 9+ 便捷方式
        List<String> immutable = List.of("A", "B", "C");
        ArrayList<String> list = new ArrayList<>(immutable);
    }
}

3.2 核心特性

它除了具有List的四大特性:有序性、可重复性、索引访问、允许null值之外,同时还具有以下特性:

  • 动态数组: ArrayList 是基于动态数组实现的,它允许存储和操作一个可变数量的元素。数组的容量会自动增长或减少,以适应元素的添加和删除操作,因此无需手动管理数组的大小。
  • 随机访问: 由于 ArrayList 是基于数组实现,因此它支持随机访问。可以通过索引来直接访问列表中的元素。
  • 线程不安全: ArrayList 不是线程安全的,这意味着在多线程环境下,如果多个线程同时访问和修改同一个 ArrayList 实例,可能会导致数据不一致或异常。如果需要在多线程环境中使用,可以考虑使用 Collections.synchronizedList() 方法来获得一个线程安全的 ArrayList
  • 性能: 由于 ArrayList 支持随机访问,因此对于需要频繁访问元素的场景非常高效。然而,在需要频繁插入或删除元素的情况下,性能可能较差,因为这涉及到数组元素的移动。
  • 动态扩容: 当 ArrayList 内部数组的容量不足以容纳新元素时,它会自动扩展数组的大小,通常是当前容量的一半。这可以避免频繁的数组重新分配操作,提高了性能。

3.3 动态扩容机制

ArrayList的扩容机制是它内部实现动态数组的关键部分,它允许 ArrayList 在需要时自动增加底层数组的容量,以适应添加元素的需求,从而避免频繁的数组重新分配操作,提高性能。以下是 ArrayList 的扩容机制的详细介绍:

  • 初始容量: ArrayList 在创建时,通常会分配一个初始容量10个元素,即使列表为空。这是为了避免在列表中只有一个元素时就立即进行扩容操作。
  • 添加元素: 当你向 ArrayList 中添加元素时,它会首先检查当前元素的数量是否达到了底层数组的容量。如果当前元素数量达到了底层数组的容量,就需要扩容。
  • 扩容策略: 当需要扩容时,它会创建一个新的更大容量的数组,而新数组的大小通常是当前容量的1.5倍(可以通过增量因子来调整,默认为1.5)。将当前数组中的所有元素复制到新数组中。新数组取代了旧数组成为 ArrayList 的底层数组。
  • 性能分析: 扩容操作是 ArrayList 中一个相对昂贵的操作,因为它需要将所有元素从旧数组复制到新数组。通常情况下,扩容的成本是 O(n) 的,其中 n 是当前 ArrayList 的大小。

四、ArrayList的常用API方法

4.1 添加元素

方法 描述 时间复杂度 示例
add(E e) 尾部添加元素 均摊O(1) list.add("Java");
add(int index, E element) 指定位置插入 O(n) list.add(1, "Python");
addAll(Collection<? extends E> c) 尾部添加集合 O(n) list.addAll(anotherList);
addAll(int index, Collection<? extends E> c) 指定位置插入集合 O(n) list.addAll(2, anotherList);
java 复制代码
// ========== add(E e) 尾部添加元素 ==========
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
System.out.println("尾部添加: " + list);              // [Java, Python]

// ========== add(int index, E element) 指定位置插入 ==========
list = new ArrayList<>(Arrays.asList("Java", "Go"));
System.out.println("原列表: " + list);                // [Java, Go]
list.add(1, "Python");
System.out.println("索引1插入Python: " + list);       // [Java, Python, Go]

// ========== addAll(Collection<? extends E> c) 尾部添加集合 ==========
list = new ArrayList<>(Arrays.asList("Java", "Python"));
List<String> others = Arrays.asList("Go", "Rust");
list.addAll(others);
System.out.println("尾部添加集合: " + list);           // [Java, Python, Go, Rust]

// ========== addAll(int index, Collection<? extends E> c)指定位置插入集合 ==========
list = new ArrayList<>(Arrays.asList("Java", "Python"));
List<String> mid = Arrays.asList("C++", "Go");
list.addAll(1, mid);
System.out.println("索引1插入集合: " + list);          // [Java, C++, Go, Python]

4.2 删除元素

方法 描述 时间复杂度 示例
remove(int index) 删除指定位置元素 O(n) list.remove(0);
remove(Object o) 删除第一个匹配元素 O(n) list.remove("Java");
removeAll(Collection<?> c) 删除包含在指定集合中的元素 O(n²) list.removeAll(toRemove);
removeIf(Predicate<? super E> filter) 按条件删除 O(n) list.removeIf(s -> s.length() < 3);
clear() 清空所有元素 O(n) list.clear();
java 复制代码
// ========== remove(int index) 删除指定位置元素 ==========
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
System.out.println("原列表: " + list);                // [A, B, C]
String removed = list.remove(1);
System.out.println("删除索引1: " + removed);           // B
System.out.println("结果: " + list);                  // [A, C]

// ========== remove(Object o) 删除第一个匹配元素 ==========
list = new ArrayList<>(Arrays.asList("A", "B", "A", "C"));
System.out.println("原列表: " + list);                // [A, B, A, C]
list.remove("A");
System.out.println("删除第一个A: " + list);            // [B, A, C]

// ========== removeAll() 删除包含在指定集合中的元素 ==========
list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
System.out.println("原列表: " + list);                // [A, B, C, D]
list.removeAll(Arrays.asList("B", "D"));
System.out.println("删除B和D: " + list);              // [A, C]

// ========== removeIf() 按条件删除 ==========
ArrayList<Integer> nums = new ArrayList<>(Arrays.asList(1, 5, 8, 12, 3));
System.out.println("原列表: " + nums);                // [1, 5, 8, 12, 3]
nums.removeIf(n -> n > 5);
System.out.println("删除>5的数: " + nums);            // [1, 5, 3]

// ========== clear() 清空所有元素 ==========
list = new ArrayList<>(Arrays.asList("X", "Y", "Z"));
System.out.println("原列表: " + list);                // [X, Y, Z]
list.clear();
System.out.println("清空后: " + list);                // []

4.3 查询元素

方法 描述 时间复杂度 示例
get(int index) 获取指定位置元素 O(1) String s = list.get(0);
indexOf(Object o) 查找元素第一次出现位置 O(n) int index = list.indexOf("Java");
lastIndexOf(Object o) 查找元素最后一次出现位置 O(n) int lastIndex = list.lastIndexOf("Java");
contains(Object o) 是否包含指定元素 O(n) boolean has = list.contains("Java");
containsAll(Collection<?> c) 是否包含集合中所有元素 O(n×m) list.containsAll(checkList);
java 复制代码
ArrayList<String> list = new ArrayList<>(Arrays.asList("Java", "Python", "Java", "Go", "Rust"));
// ========== get(int index) 获取指定位置元素 ==========
String element = list.get(1);
System.out.println("索引1的元素: " + element);            // Python

// ========== indexOf(Object o) 查找元素第一次出现位置 ==========
int firstIndex = list.indexOf("Java");
System.out.println("Java第一次出现位置: " + firstIndex);  // 0

// ========== lastIndexOf(Object o) 查找元素最后一次出现位置 ==========
int lastIndex = list.lastIndexOf("Java");
System.out.println("Java最后一次出现位置: " + lastIndex); // 2

// ========== contains(Object o) 是否包含指定元素 ==========
boolean hasGo = list.contains("Go");
boolean hasC = list.contains("C++");
System.out.println("是否包含Go: " + hasGo);               // true
System.out.println("是否包含C++: " + hasC);               // false

// ========== containsAll(Collection<?> c) 是否包含集合中所有元素 ==========
List<String> check1 = Arrays.asList("Java", "Python");
List<String> check2 = Arrays.asList("Java", "C++");

boolean hasAll1 = list.containsAll(check1);
boolean hasAll2 = list.containsAll(check2);

System.out.println("是否包含[Java, Python]: " + hasAll1);  // true
System.out.println("是否包含[Java, C++]: " + hasAll2);     // false

4.4 修改元素

方法 描述 时间复杂度 示例
set(int index, E element) 替换指定位置元素 O(1) list.set(0, "NewValue");
retainAll(Collection<?> c) 保留指定集合中的元素 O(n²) list.retainAll(keepList);
replaceAll(UnaryOperator<E> op) 对每个元素应用操作 O(n) list.replaceAll(String::toUpperCase);
java 复制代码
// ========== set(int index, E element) 替换指定位置元素 ==========
ArrayList<String> list = new ArrayList<>(Arrays.asList("Java", "Python", "Go"));
System.out.println("原列表: " + list);                    // [Java, Python, Go]

String old = list.set(1, "C++");
System.out.println("被替换的值: " + old);                 // Python
System.out.println("替换后: " + list);                    // [Java, C++, Go]

// ========== retainAll(Collection<?> c) 保留指定集合中的元素 ==========
list = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));
System.out.println("原列表: " + list);                    // [A, B, C, D, E]

List<String> keep = Arrays.asList("B", "D", "F");
list.retainAll(keep);
System.out.println("保留[B, D, F]后: " + list);           // [B, D]

// ========== replaceAll(UnaryOperator<E> op) 对每个元素应用操作 ==========
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
System.out.println("原数字: " + numbers);                 // [1, 2, 3, 4, 5]

numbers.replaceAll(n -> n * 2);
System.out.println("每个数乘2: " + numbers);              // [2, 4, 6, 8, 10]

ArrayList<String> words = new ArrayList<>(Arrays.asList("java", "python", "go"));
System.out.println("原单词: " + words);                   // [java, python, go]

words.replaceAll(String::toUpperCase);
System.out.println("转大写: " + words);                   // [JAVA, PYTHON, GO]

4.5 大小与容量

方法 描述 时间复杂度 示例
size() 返回元素个数 O(1) int size = list.size();
isEmpty() 判断是否为空 O(1) boolean empty = list.isEmpty();
trimToSize() 将容量调整为当前大小 O(1) list.trimToSize();
ensureCapacity(int minCapacity) 确保至少能容纳指定数量元素 O(1) list.ensureCapacity(100);
java 复制代码
// ========== size() 返回元素个数 ==========
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
System.out.println("列表: " + list);                     // [A, B, C]
int size = list.size();
System.out.println("元素个数: " + size);                 // 3

// ========== isEmpty() 判断是否为空 ==========
System.out.println("是否为空: " + list.isEmpty());       // false
list.clear();
System.out.println("清空后是否为空: " + list.isEmpty()); // true

// ========== ensureCapacity() 将容量调整为当前大小 ==========
ArrayList<Integer> numbers = new ArrayList<>();
System.out.println("初始大小: " + numbers.size());       // 0

// 预分配容量,避免频繁扩容
numbers.ensureCapacity(1000);

for (int i = 1; i <= 100; i++) {
    numbers.add(i);
}
System.out.println("添加100个元素后大小: " + numbers.size()); // 100

// ========== trimToSize() 确保至少能容纳指定数量元素 ==========
ArrayList<String> trimList = new ArrayList<>(100);  // 初始容量100
trimList.add("X");
trimList.add("Y");
trimList.add("Z");

System.out.println("实际元素: " + trimList);          // [X, Y, Z]
System.out.println("实际大小: " + trimList.size());   // 3

// 将容量缩减到与实际大小一致,节省内存
trimList.trimToSize();
System.out.println("容量已调整为3");

4.6 遍历与转换

方法 描述 时间复杂度 示例
iterator() 返回迭代器 O(1) Iterator<String> it = list.iterator();
listIterator() 返回列表迭代器 O(1) ListIterator<String> lit = list.listIterator();
forEach(Consumer<? super E> action) 遍历每个元素 O(n) list.forEach(System.out::println);
toArray() 转换为数组 O(n) Object[] arr = list.toArray();
toArray(T[] a) 转换为指定类型数组 O(n) String[] arr = list.toArray(new String[0]);
subList(int fromIndex, int toIndex) 获取子列表 O(1) List<String> sub = list.subList(1, 3);
java 复制代码
ArrayList<String> list = new ArrayList<>(Arrays.asList("Java", "Python", "Go", "Rust"));
System.out.println("原列表: " + list);                    // [Java, Python, Go, Rust]

// ========== iterator() 返回迭代器 ==========
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    System.out.print(it.next() + " ");                   // Java Python Go Rust 
}

// ========== listIterator() 返回列表迭代器 ==========
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {                                  // 正向遍历
    System.out.print(lit.next() + " ");                  // Java Python Go Rust 
}

while (lit.hasPrevious()) {                              // 反向遍历
    System.out.print(lit.previous() + " ");              // Rust Go Python Java 
}

// ========== forEach() 遍历每个元素 ==========
list.forEach(item -> System.out.print(item + " "));      // Java Python Go Rust 

// ========== toArray() 转换为数组 ==========
Object[] objArray = list.toArray();
System.out.println("Object数组: " + Arrays.toString(objArray)); // [Java, Python, Go, Rust]

// ========== toArray(T[] a) ==========
String[] strArray = list.toArray(new String[0]);
System.out.println("String数组: " + Arrays.toString(strArray)); // [Java, Python, Go, Rust]

// --------------------------------------

// ========== subList() 获取子列表 ==========
List<String> subList = list.subList(1, 3);
System.out.println("子列表(1-3): " + subList);            // [Python, Go]

// 注意:子列表与原列表共享数据
subList.set(0, "C++");
System.out.println("修改子列表后原列表: " + list);        // [Java, C++, Go, Rust]

4.7 排序与比较

方法 描述 时间复杂度 示例
sort(Comparator<? super E> c) 排序 O(n log n) list.sort(Comparator.naturalOrder());
equals(Object o) 比较是否相等 O(n) boolean eq = list.equals(otherList);
hashCode() 返回哈希码 O(n) int hash = list.hashCode();
java 复制代码
// ========== sort() 排序 ==========
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9));
System.out.println("原数字: " + numbers);                 // [5, 2, 8, 1, 9]

numbers.sort(Comparator.naturalOrder());
System.out.println("升序排序: " + numbers);               // [1, 2, 5, 8, 9]

numbers.sort(Comparator.reverseOrder());
System.out.println("降序排序: " + numbers);               // [9, 8, 5, 2, 1]

ArrayList<String> words = new ArrayList<>(Arrays.asList("banana", "apple", "cat"));
words.sort(Comparator.naturalOrder());
System.out.println("字符串排序: " + words);               // [apple, banana, cat]

words.sort((a, b) -> a.length() - b.length());
System.out.println("按长度排序: " + words);               // [cat, apple, banana]

// ========== equals() 比较是否相等 ==========
ArrayList<String> list1 = new ArrayList<>(Arrays.asList("A", "B", "C"));
ArrayList<String> list2 = new ArrayList<>(Arrays.asList("A", "B", "C"));
ArrayList<String> list3 = new ArrayList<>(Arrays.asList("A", "C", "B"));

System.out.println("list1: " + list1);                    // [A, B, C]
System.out.println("list2: " + list2);                    // [A, B, C]
System.out.println("list3: " + list3);                    // [A, C, B]

boolean eq1 = list1.equals(list2);
boolean eq2 = list1.equals(list3);

System.out.println("list1 等于 list2: " + eq1);           // true
System.out.println("list1 等于 list3: " + eq2);           // false

// ========== hashCode() 返回哈希码 ==========
int hash1 = list1.hashCode();
int hash2 = list2.hashCode();
int hash3 = list3.hashCode();

System.out.println("list1哈希码: " + hash1);
System.out.println("list2哈希码: " + hash2);
System.out.println("list3哈希码: " + hash3);
System.out.println("list1和list2哈希码相同: " + (hash1 == hash2)); // true
System.out.println("list1和list3哈希码相同: " + (hash1 == hash3)); // false

五、ArrayList的常见注意事项

5.1 Arrays.asList()返回的List不可改变大小

Arrays.asList() 返回的 List 并非完全不可变 ,而是固定大小的 List,其关键特征如下:

  1. 底层是固定数组 :返回的List直接使用原始数组存储数据,数组长度固定,无法真正添加或删除元素
  2. 视图而非副本 :它是数组的一个List视图,任何修改都会反映到原数组上

不支持的操作(结构性修改)

java 复制代码
List<String> list = Arrays.asList("A", "B", "C");

// 以下操作会抛出 UnsupportedOperationException
list.add("D");      // 添加元素
list.remove(0);     // 删除元素
list.clear();       // 清空列表

支持的操作

java 复制代码
List<String> list = Arrays.asList("A", "B", "C");

// 以下操作可以正常工作
list.set(1, "X");   // 修改元素 - 成功
System.out.println(list.get(0));  // 访问元素
System.out.println(list.size());  // 获取大小

如果需要可变的List,用ArrayList包装

java 复制代码
String[] fruits = {"Apple", "Banana", "Cherry"};
List<String> mutableList1 = new ArrayList<>(Arrays.asList(fruits));
mutableList1.add("Date");
mutableList1.remove(0);
System.out.println("可变List: " + mutableList1);

5.2 foreach遍历时不能修改

foreach 循环实际上是使用 Iterator(迭代器)的语法糖:

  • 迭代器会维护一个 modCount(修改次数)计数器
  • 每次调用 iterator.next() 时,会检查集合的修改次数是否与迭代器期望的一致
  • 直接调用 list.remove() 会修改集合的 modCount,但迭代器不知道这个变化
  • 下次调用 next() 时发现不一致,立即抛出 ConcurrentModificationException
java 复制代码
// 你写的代码
for (String item : list) {
    if (item.equals("a")) {
        list.remove(item);
    }
}

// 编译后等价于
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("a")) {
        list.remove(item);  // 通过集合直接修改
    }
}

解决方案

方案1:使用 Iterator 的 remove 方法

java 复制代码
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("a")) {
        iterator.remove();  // 通过迭代器删除,不会抛出异常
    }
}

方案2:使用 for 循环倒序遍历

java 复制代码
// 从后往前遍历,避免索引变化问题
for (int i = list.size() - 1; i >= 0; i--) {
    if (list.get(i).equals("a")) {
        list.remove(i);
    }
}

方案3:使用 Java 8+ 的 removeIf

java 复制代码
list.removeIf(item -> item.equals("a"));  // 最简单,一行搞定

5.3 添加对象时候重写equals方法

java 复制代码
// 创建第一个"Alice"
Person p1 = new Person("Alice");
list.add(p1);

// 创建第二个"Alice"
Person p2 = new Person("Alice");

// 问:list里有"Alice"吗?
list.contains(p2);  // false ❌
  • p1 和 p2 是两个不同的东西(就像双胞胎,长得一样但不是同一个人)
  • 除非你告诉 Java:"只要名字一样,就认为是同一个人"

所以我们需要重写equals方法,当然现在很多开发时候都会使用Lombok注解中的@Data,这个会自动为我们重写equals方法

java 复制代码
class Person {
    String name;
    
    // 告诉 Java:比较两个人时,只比较名字就行
    @Override
    public boolean equals(Object obj) {
        Person p = (Person) obj;
        return this.name.equals(p.name);  // 名字一样就是同一个人
    }
}
相关推荐
用户7344028193422 小时前
Docker 部署 Spring Boot 项目完整指南:从零到生产环境
后端
用户7344028193422 小时前
Docker配置daemon.json
后端
架构师沉默2 小时前
女孩去旅行,给男朋友带回了一个难解的 Bug
java·后端·架构
月光宝鉴2 小时前
如何将excel中文件转为json
后端
xu_ws2 小时前
Spring-ai项目-deepseek-6-哄哄模拟器
java·人工智能·spring
刘 大 望2 小时前
SpringAI Tool Calling(工具调用)
java·spring boot·spring·ai·maven·intellij-idea·文心一言
222you2 小时前
Java 并发编程(1)
java·开发语言
stark张宇2 小时前
告别混乱接口:RESTful API 规范实战指南
后端