前端转战后端:JavaScript 与 Java 对照学习指南(第四篇 —— List)

前端工程师写惯了 JavaScript 的数组,一开始接触 Java 的集合时很容易迷茫:

  • "为什么 List 要分 ArrayList、LinkedList?"
  • "为什么不能像 JS 一样直接 arr[0] 用?"
  • "为什么 List 删除要区分 remove(索引) 和 remove(对象)?"
  • "为什么要写 List 而不是 List?"

本篇将从 JavaScript 的视角,完整讲清楚 Java 的 List 体系,并帮助你在后端开发中真正用明白。


🟦 1. JavaScript 数组 vs Java List:本质差异

✔ JavaScript 数组的本质:动态对象 & 哈希结构

JavaScript 数组不是严格意义的"数组",底层类似:

JavaScript 复制代码
{
  "0": 10,
  "1": "abc",
  "2": true,
  length: 3
}

特点:

  • 类型随便放(动态类型)
  • 可随便扩容
  • 可缺省元素([1, , 3])
  • 很多场景是 JS 引擎优化过的对象

对前端来说,这非常自由。


✔ Java 的 List 本质:严格类型、固定结构、接口规范

Java 的 List 是一个接口:

Java 复制代码
public interface List<E> extends Collection<E> {
    ...
}

所有 List 只能存放同一类型 E(泛型)。

List 有多种实现方式:

实现类 底层结构 类比 JS
ArrayList 动态数组 标准数组
LinkedList 双向链表 无直接等价(手写链表)
CopyOnWriteArrayList 线程安全 JS 无对应

🟩 2. ArrayList:前端最先上手的 List

⭐ 底层原理(重点)

ArrayList 底层是 动态数组

Java 复制代码
Object[] elementData;

默认容量 10,满了之后 1.5 倍扩容

扩容做的事类似:

Java 复制代码
新数组 = new Object[旧容量 * 1.5]
把旧数组 copy 过去

扩容成本较高,所以:

后端开发中频繁往 ArrayList 加东西时,建议预估一下初始容量。

示例:

Java 复制代码
List<Integer> list = new ArrayList<>(1000);

⭐ 常用 API 全对照 JS

操作 JavaScript Java ArrayList
创建 const arr = [] List<T> list = new ArrayList<>();
添加尾部 arr.push(x) list.add(x)
插入 arr.splice(i,0,x) list.add(i, x)
获取 arr[i] list.get(i)
设置 arr[i] = x list.set(i, x)
删除(按索引) arr.splice(i,1) list.remove(i)
删除(按值) arr = arr.filter(v => v != x) list.remove("A")
长度 arr.length list.size()
查找索引 arr.indexOf(x) list.indexOf(x)

⭐ 性能分析(简单记住即可)

操作 复杂度 原因
get(i) O(1) 数组寻址
set(i) O(1) 数组寻址
add(x) 尾部 摊销 O(1) 偶尔扩容
add(i,x) 中间插入 O(n) 需要移动元素
remove(i) O(n) 需要移动元素

结论:

ArrayList = 读多写少的场景最佳选择

在后端,90% 业务都是 "读多写少"。


🟨 3. LinkedList:链表 List

如果你写过 LeetCode 链表题,你就懂 LinkedList。

⭐ 底层结构

双向链表:

Java 复制代码
prev ← [node] → next

插入/删除只需要:

  1. 找到节点
  2. 改指针

但无法随机访问。


⭐ 性能对比

操作 ArrayList LinkedList 为什么
get(i) O(1) O(n) 链表需要遍历
add(i,x) O(n) O(n) ArrayList 移动元素,LinkedList 找节点
addFirst O(n) O(1) LinkedList 不移动元素
removeFirst O(n) O(1) 同上

因此:

LinkedList 的中间插入删除不是真正的 O(1),因为必须先找到节点!

很多前端误解:"链表插入 O(1) 啊",那是指"找到节点之后"。


🟦 4. List 遍历方式(对比 JS)

✔ JavaScript(简单)

JavaScript 复制代码
arr.forEach(item => console.log(item));

✔ Java 有 4 种常见遍历方式

① 普通 for(可随机访问)

Java 复制代码
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

② 增强 for

Java 复制代码
for (String s : list) {
    System.out.println(s);
}

③ Iterator(可安全删除元素)

Java 复制代码
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("A")) {
        it.remove(); // 安全
    }
}

对照 JS:

JavaScript 复制代码
arr = arr.filter(x => x !== "A");

④ Stream(最像 JS 函数式)

JavaScript 复制代码
list.stream()
    .filter(x -> x.startsWith("A"))
    .forEach(System.out::println);

对应 JS:

JavaScript 复制代码
arr.filter(x => x.startsWith("A")).forEach(console.log);

🟥 5. List 的 7 个常见坑(前端最容易踩)

❗坑 1:不能 list[0]

JS:

JavaScript 复制代码
arr[0]

Java:

Java 复制代码
list.get(0)

为什么不给支持?

因为 List 是接口,不保证一定是数组结构,比如 LinkedList。


❗坑 2:删除整数值可能错用 remove(int)

Java 复制代码
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.remove(1);

你以为删除 "1",实际删除索引 1(值 2)...

正确写法:

Java 复制代码
list.remove(Integer.valueOf(1));

❗坑 3:List.of() 是不可变的(Java 9+)

Java 复制代码
List<String> list = List.of("A", "B");
list.add("C"); // ❌ 运行时报错 UnsupportedOperationException

JS 的 Object.freeze() 类似。


❗坑 4:Arrays.asList() 不是完全可变

Java 复制代码
List<String> list = Arrays.asList("A", "B");
list.add("C"); // ❌ 不支持添加/删除

因为它的底层是固定数组。


❗坑 5:不要在增强 for 中删除元素

Java 复制代码
for(String s : list) {
    list.remove(s); // ❌ ConcurrentModificationException
}

要用 Iterator:

Java 复制代码
Iterator<String> it = list.iterator();
it.remove();

❗坑 6:代码中频繁 new ArrayList() 会影响性能

后端开发中常见:

Java 复制代码
List<User> users = new ArrayList<>();
for (...) {
    users = new ArrayList<>(); // ❌ 无意义开销
}

要注意避免反复建立对象。


❗坑 7:在高并发下 ArrayList 不是线程安全的

如果在多线程中使用:

Java 复制代码
List<String> unsafe = new ArrayList<>();

可能引发异常、数据错乱。

正确方式:

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

或使用:

Java 复制代码
CopyOnWriteArrayList<String> safeList = new CopyOnWriteArrayList<>();

🟧 6. 后端实战场景示例

下面举几个开发中常见的 List 使用示例。


场景 1:查询数据库返回 List

Java 复制代码
List<User> users = userDao.queryAllUsers();

JS 类比:

JavaScript 复制代码
const users = await api.getUsers();

场景 2:遍历用户并构造 DTO

Java:

Java 复制代码
List<UserDTO> dtos = new ArrayList<>();
for (User user : users) {
    dtos.add(new UserDTO(user.getId(), user.getName()));
}

JS:

JavaScript 复制代码
const dtos = users.map(u => ({ id: u.id, name: u.name }));

场景 3:去重(Java 要借助 Set)

Java 复制代码
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "A"));
List<String> result = new ArrayList<>(new HashSet<>(list));

JS:

JavaScript 复制代码
const result = [...new Set(["A", "B", "A"])];

场景 4:按条件过滤

Java:

Java 复制代码
List<User> adults = users.stream()
    .filter(u -> u.getAge() >= 18)
    .toList();

JS:

JavaScript 复制代码
const adults = users.filter(u => u.age >= 18);

🟫 7. ArrayList vs LinkedList:详细对比总结

类型 底层 优点 缺点 使用场景
ArrayList 动态数组 随机访问快,内存连续 插入/删除慢,扩容成本高 大部分业务使用
LinkedList 双向链表 插入删除快(找到节点后) 随机访问慢,内存分散 队列、头部操作频繁

后端实际经验:

  • 95% 用 ArrayList
  • LinkedList 很少用,因为场景不常见

🎉 总结

对于从前端转后端的开发者:

✔ 把 ArrayList 当 JS 数组用足够了

✔ LinkedList 除非你确定需要,否则不要用

✔ List 操作与 JS 高度类比,功能更严格

✔ 泛型保证类型安全

✔ 遍历方式更多(for / for-each / Iterator / Stream)

✔ 注意一些常见坑(remove、不可变 List、线程安全等)

相关推荐
Cache技术分享1 小时前
260. Java 集合 - 深入了解 HashSet 的内部结构
前端·后端
前端老宋Running1 小时前
你的代码在裸奔?给 React 应用穿上“防弹衣”的保姆级教程
前端·javascript·程序员
FinClip1 小时前
当豆包手机刷屏时,另一场“静悄悄”的变革已经在你手机里发生
前端
前端老宋Running1 小时前
“求求你别在 JSX 里写逻辑了” —— Headless 思想与自定义 Hook 的“灵肉分离”术
前端·javascript·程序员
阿珊和她的猫1 小时前
深入理解 HTML 中 `<meta>` 标签的 `charset` 和 `http-equiv` 属性
前端·http·html
汉堡大王95271 小时前
告别"回调地狱"!Promise让异步代码"一线生机"
前端·javascript
syt_10131 小时前
gird布局之九宫格布局
前端·javascript·css
BD_Marathon1 小时前
【JavaWeb】HTML_常见标签_布局相关标签
前端·html
SelectDB技术团队1 小时前
云上数据安全新范式:Apache Doris IAM Assume Role 解锁无密钥访问 AWS S3 数据
服务器·前端·安全