WSの小屋

Java SE 从入门到精通

一份系统的 Java 标准版学习指南,覆盖所有核心知识点。

1. Java 简介与环境搭建

1.1 什么是 Java?

Java 是一种面向对象跨平台的编程语言。核心特点:

特点 说明
跨平台 "Write Once, Run Anywhere"——JVM 屏蔽操作系统差异
面向对象 一切皆对象,支持封装、继承、多态
自动内存管理 JVM 垃圾回收(GC)自动回收无用对象
生态丰富 海量开源库和框架(Spring、MyBatis 等)

1.2 JDK vs JRE vs JVM

Java 程序运行结构:

┌─────────────────────────────────────────┐
│           JDK(开发工具包)               │
│  ┌──────────────────────────────────┐   │
│  │     JRE(运行时环境)              │   │
│  │  ┌────────────────────────────┐  │   │
│  │  │   JVM(Java 虚拟机)        │  │   │
│  │  └────────────────────────────┘  │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘
组件 全称 用途
JVM Java Virtual Machine 运行 Java 字节码
JRE Java Runtime Environment JVM + 核心类库,运行 Java 程序用
JDK Java Development Kit JRE + 开发工具(javac、jar 等),开发 Java 程序用

1.3 编译与执行流程

.java 源文件 ──[javac 编译]──> .class 字节码 ──[java 运行]──> JVM 执行
javac HelloWorld.java   # 编译:生成 HelloWorld.class
java HelloWorld          # 运行

2. 程序基本结构

2.1 Hello World

/**
 * 类名必须和文件名一致
 * 程序入口:main 方法(固定写法)
 */
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");   // 输出并换行
        System.out.print("不换行输出");          // 输出不换行
    }
}

2.2 注释

// 单行注释

/*
 * 多行注释
 * 可以写多行
 */

/**
 * 文档注释(Javadoc)
 * 可以用 javadoc 命令生成 API 文档
 *
 * @author wsvaio
 * @version 1.0
 */

2.3 标识符命名规则

规则 示例
由字母、数字、下划线 _、美元符 $ 组成 userName$value
不能以数字开头 1namename1
不能是关键字或保留字 classint
区分大小写 nameName 不同
驼峰命名法 类名 UserService,变量 userName,常量 MAX_VALUE

2.4 关键字一览

abstract    assert      boolean     break       byte
case        catch       char        class       const(保留)
continue    default     do          double      else
enum        extends     final       finally     float
for         goto(保留)  if          implements  import
instanceof  int         interface   long        native
new         package     private     protected   public
return      short       static      strictfp    super
switch      synchronized this       throw       throws
transient   try         void        volatile    while

3. 数据类型与变量

3.1 基本数据类型(8种)

类型 大小 取值范围 默认值 示例
byte 1字节 -128 ~ 127 0 byte b = 10;
short 2字节 -32768 ~ 32767 0 short s = 100;
int 4字节 -21亿 ~ 21亿 0 int i = 1000;
long 8字节 -9.22e18 ~ 9.22e18 0L long l = 100L;
float 4字节 ±3.4e38 0.0f float f = 3.14f;
double 8字节 ±1.8e308 0.0d double d = 3.14;
char 2字节 0 ~ 65535 (Unicode) '\u0000' char c = 'A';
boolean 未定义 true / false false boolean flag = true;

3.2 引用类型

引用类型存储对象的内存地址(指针),指向堆内存中的对象。

String name = "Alice";           // String 类型
int[] scores = new int[5];       // 数组
User user = new User();          // 类
List<String> list = new ArrayList<>(); // 接口

3.3 类型转换

自动类型转换(小→大):

byte → short → int → long → float → double
                ↑
               char
int i = 100;
long l = i;        // 自动转:int → long
float f = l;       // 自动转:long → float

强制类型转换(大→小,可能丢失精度):

double d = 3.99;
int i = (int) d;   // i = 3(截断小数)

3.4 包装类

每个基本类型都有对应的包装类,支持自动装箱/拆箱。

基本类型 包装类 缓存范围
byte Byte -128 ~ 127
short Short -128 ~ 127
int Integer -128 ~ 127
long Long -128 ~ 127
float Float
double Double
char Character 0 ~ 127
boolean Boolean true / false
// 自动装箱:int → Integer
Integer n = 100;

// 自动拆箱:Integer → int
int m = n;

// 注意:缓存范围内的 Integer 用 == 比较为 true
Integer a = 127;
Integer b = 127;
System.out.println(a == b);       // true(缓存命中)

Integer c = 128;
Integer d = 128;
System.out.println(c == d);       // false(超出缓存,不同对象)
System.out.println(c.equals(d));  // true(内容相同)

3.5 变量

// 成员变量(在类中,方法外)
public class User {
    private String name;    // 实例变量(属于对象)
    private static int count; // 类变量(属于类,所有对象共享)
}

// 局部变量(在方法中,必须初始化)
public void sayHello() {
    String msg = "Hello";   // 局部变量
    int x;                  // ❌ 未初始化,不能使用
}

4. 运算符

类型 运算符 说明
算术 + - * / % % 取余,整数除法截断:5/2=2
自增自减 ++ -- i++ 先用后加,++i 先加后用
关系 == != > < >= <= 返回 boolean
逻辑 && || ! 短路运算
位运算 & | ~ ^ << >> >>> 二进制位操作
三元 条件 ? 值1 : 值2 简洁的条件表达式
赋值 = += -= *= /= %= 复合赋值
// 算术
int a = 10, b = 3;
System.out.println(a / b);   // 3(整数除法)
System.out.println(a % b);   // 1(取余)

// 自增自减
int x = 5;
int y = x++;   // y=5, x=6(先用后加)
int z = ++x;   // z=7, x=7(先加后用)

// 短路逻辑
String s = null;
if (s != null && s.length() > 0) { }  // 安全:s != null 为 false,后面不执行

// 三元
int max = (a > b) ? a : b;

// 位运算
int flag = 0b1010;          // 二进制字面量
int result = flag << 2;     // 左移2位 → 0b101000

5. 流程控制

5.1 条件判断

// if-else
int score = 85;
if (score >= 90) {
    System.out.println("优秀");
} else if (score >= 60) {
    System.out.println("及格");
} else {
    System.out.println("不及格");
}

// switch(支持 byte/short/int/char/String/enum)
int day = 3;
switch (day) {
    case 1:
        System.out.println("周一");
        break;
    case 2:
        System.out.println("周二");
        break;
    default:
        System.out.println("其他");
}

// Java 14+ switch 表达式(更简洁)
String result = switch (day) {
    case 1, 2, 3, 4, 5 -> "工作日";
    case 6, 7 -> "周末";
    default -> "无效";
};

5.2 循环

// for 循环
for (int i = 0; i < 5; i++) {
    System.out.println(i);
}

// 增强 for 循环(for-each)
int[] arr = {1, 2, 3};
for (int num : arr) {
    System.out.println(num);
}

// while 循环
int i = 0;
while (i < 5) {
    System.out.println(i++);
}

// do-while(至少执行一次)
int j = 0;
do {
    System.out.println(j++);
} while (j < 5);

5.3 跳转语句

break;      // 跳出当前循环
continue;   // 跳过本次循环,进入下一次
return;     // 结束方法

// 带标签的 break/continue(跳出多层循环)
outer:
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (i * j > 6) break outer;  // 直接跳出两层循环
    }
}

6. 数组

6.1 声明与初始化

// 声明方式
int[] arr1;          // 推荐
int arr2[];          // C 语言风格,不推荐

// 初始化
int[] a = new int[5];          // 默认值 0
int[] b = {1, 2, 3};           // 静态初始化
int[] c = new int[]{1, 2, 3};  // 动态初始化

// 二维数组
int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

6.2 常用操作

int[] arr = {3, 1, 4, 1, 5, 9};

arr.length;                        // 数组长度
Arrays.sort(arr);                  // 排序
Arrays.toString(arr);              // 转字符串 [3, 1, 4, 1, 5, 9]
Arrays.fill(arr, 0);               // 填充为 0
Arrays.copyOf(arr, 10);            // 复制(扩容)
Arrays.copyOfRange(arr, 0, 3);     // 复制指定范围
Arrays.binarySearch(arr, 5);       // 二分查找(必须先排序)
Arrays.equals(arr1, arr2);         // 比较两个数组内容是否相等
Arrays.deepToString(matrix);       // 打印多维数组

6.3 数组内存分析

int[] arr = new int[3];
arr[0] = 10;
arr[1] = 20;
栈内存(Stack)          堆内存(Heap)
┌──────────┐           ┌──────────────┐
│ arr  ●───┼──────────►│ [0] 10       │
│          │           │ [1] 20       │
│          │           │ [2] 0        │
└──────────┘           └──────────────┘

7. 字符串

7.1 创建字符串

String s1 = "Hello";                // 字面量 → 字符串常量池
String s2 = new String("Hello");    // new → 堆内存
String s3 = "Hello";                // 指向常量池同一对象

s1 == s2;          // false(地址不同)
s1 == s3;          // true(常量池复用)
s1.equals(s2);     // true(内容相同)

7.2 常用方法

String s = "  Hello, World!  ";

s.length();                        // 长度
s.isEmpty();                       // 是否为空
s.isBlank();                       // 是否为空白(JDK11+)

s.charAt(0);                       // 获取指定位置的字符
s.substring(0, 5);                 // 截取 [0,5) → "Hello"
s.split(",");                      // 分割 → ["  Hello", " World!  "]
s.replace("l", "x");               // 替换 → "  Hexxo, Worxd!  "
s.replaceAll("\\s", "");           // 正则替换

s.trim();                          // 去除首尾空格 → "Hello, World!"
s.strip();                         // 去除首尾空白(JDK11+,支持全角空格)
s.toUpperCase();                   // 大写
s.toLowerCase();                   // 小写

s.startsWith("Hello");             // 是否以...开头
s.endsWith("!");                   // 是否以...结尾
s.contains("World");               // 是否包含
s.indexOf("o");                    // 首次出现的位置
s.lastIndexOf("o");                // 最后出现的位置

s.equals("hello");                 // 内容相等
s.equalsIgnoreCase("HELLO");       // 忽略大小写

String.valueOf(123);               // 其他类型转字符串
Integer.parseInt("123");           // 字符串转 int
Double.parseDouble("3.14");        // 字符串转 double

7.3 String 不可变性

String s = "Hello";
s = s + " World";  // 不是修改原对象,而是创建新对象
"Hello" ─────────────► 字符串常量池
      ↓
"Hello World" ◄───── 新对象

7.4 String vs StringBuilder vs StringBuffer

可变性 线程安全 性能 使用场景
String 不可变 安全 拼接慢(产生大量中间对象) 少量字符串操作
StringBuilder 可变 不安全 最快 单线程大量拼接 ✅
StringBuffer 可变 安全(synchronized) 较慢 多线程字符串操作
// 大量拼接时使用 StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString();  // "Hello World"

8. 面向对象基础

8.1 类与对象

// 类的定义
public class Student {
    // 属性(成员变量)
    private String name;
    private int age;

    // 构造方法
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 方法
    public void study() {
        System.out.println(name + " 正在学习");
    }

    // Getter / Setter
    public String getName() {
        return name;
    }

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

// 创建对象
Student stu = new Student("Alice", 20);
stu.study();

8.2 三大特性之一:封装

隐藏内部实现细节,对外暴露接口。

public class BankAccount {
    private double balance;  // 私有属性,外部不能直接访问

    public void deposit(double amount) {   // 对外暴露的方法
        if (amount > 0) {
            balance += amount;
        }
    }

    public double getBalance() {           // 只读,不能修改
        return balance;
    }
}

8.3 三大特性之二:继承

子类继承父类的属性和方法,实现代码复用。

// 父类
public class Animal {
    protected String name;

    public void eat() {
        System.out.println(name + " 在吃饭");
    }
}

// 子类
public class Dog extends Animal {
    public void bark() {
        System.out.println("汪汪");
    }

    // 方法重写(Override)
    @Override
    public void eat() {
        System.out.println(name + " 在啃骨头");
    }
}

super 关键字

public class Dog extends Animal {
    public Dog(String name) {
        super();           // 调用父类构造器(必须放在第一行)
        this.name = name;
    }

    public void eat() {
        super.eat();       // 调用父类方法
        System.out.println("然后啃骨头");
    }
}

继承的限制

  • Java 只支持单继承(一个类只能有一个直接父类)
  • 可以通过接口实现多重继承的效果

8.4 三大特性之三:多态

同一个方法在不同对象中有不同的行为。

三个必要条件

  1. 继承(或实现接口)
  2. 方法重写
  3. 父类引用指向子类对象
// 父类
class Animal {
    void sound() { System.out.println("动物叫"); }
}

// 子类
class Cat extends Animal {
    @Override
    void sound() { System.out.println("喵喵"); }
}

class Dog extends Animal {
    @Override
    void sound() { System.out.println("汪汪"); }
}

// 多态
public class Test {
    public static void main(String[] args) {
        Animal a1 = new Cat();         // 向上转型
        Animal a2 = new Dog();

        a1.sound();  // 喵喵(实际调用的是子类方法)
        a2.sound();  // 汪汪

        // 向下转型(恢复子类特有方法)
        if (a1 instanceof Cat) {
            Cat cat = (Cat) a1;        // 安全地向下转型
        }
    }
}

8.5 static 关键字

public class Counter {
    public static int count = 0;       // 类变量(所有对象共享)
    private int id;                    // 实例变量(每个对象独有)

    public Counter() {
        count++;
        this.id = count;
    }

    public static void showCount() {   // 类方法
        System.out.println("总数: " + count);
        // System.out.println(id);     // ❌ 静态方法不能访问实例变量
    }
}

Counter c1 = new Counter();
Counter c2 = new Counter();
Counter.showCount();                   // 总数: 2

8.6 final 关键字

位置 作用
final class 不能被继承(如 StringMath
final method 不能被重写
final variable 常量,值不可变(必须初始化)
public final class Math { ... }           // 不可继承

public class Parent {
    public final void show() { ... }      // 不可重写
}

public class Student {
    public static final int MAX_SCORE = 100;  // 常量(大写命名)
    public final String name;                  // 实例常量

    public Student(String name) {
        this.name = name;   // final 字段必须在构造器中初始化
    }
}

8.7 抽象类与抽象方法

// 抽象类(不能直接实例化)
public abstract class Shape {
    protected String color;

    // 抽象方法(没有方法体,子类必须实现)
    public abstract double getArea();

    // 普通方法
    public void setColor(String color) {
        this.color = color;
    }
}

public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

8.8 接口

接口定义契约(一组抽象方法),实现类必须实现所有方法。

// 接口定义(JDK 8+)
public interface Payment {
    // 抽象方法(实现类必须实现)
    void pay(double amount);

    // 默认方法(JDK 8,有默认实现)
    default void printReceipt() {
        System.out.println("打印小票");
    }

    // 静态方法(JDK 8)
    static boolean validateAmount(double amount) {
        return amount > 0;
    }
}

// 实现接口
public class WeChatPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付: " + amount);
    }
}

接口 vs 抽象类

对比 抽象类 接口
关键字 abstract class interface
构造方法 可以有 不能有
成员变量 任意 public static final 常量
方法 抽象/非抽象都可以 抽象/默认/静态方法
继承/实现 单继承 多实现
语义 "是什么"(is-a) "能做什么"(can-do)

9. 面向对象进阶

9.1 内部类

// 成员内部类
public class Outer {
    private int x = 10;

    class Inner {
        public void show() {
            System.out.println(x);  // 可以访问外部类私有成员
        }
    }
}
Outer.Inner inner = new Outer().new Inner();

// 静态内部类
class Outer {
    static class StaticInner { }
}
Outer.StaticInner si = new Outer.StaticInner();

// 局部内部类(定义在方法中)
class Outer {
    public void method() {
        class LocalInner { }
    }
}

// 匿名内部类(常用于事件监听、线程等)
Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("执行任务");
    }
};

9.2 枚举

// 枚举定义
public enum Season {
    SPRING("春"), SUMMER("夏"), AUTUMN("秋"), WINTER("冬");

    private final String name;

    Season(String name) {    // 构造方法(默认 private)
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

// 使用枚举
Season s = Season.SPRING;
System.out.println(s.getName());  // 春

// 遍历枚举
for (Season season : Season.values()) {
    System.out.println(season.ordinal() + ": " + season.getName());
}

9.3 方法重载(Overload)

同一个类中,方法名相同,参数列表不同。

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public double add(double a, double b) {
        return a + b;
    }

    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

9.4 方法重写(Override)

子类重新定义父类的方法。

// @Override 注解(可选,推荐加,编译器会检查是否正确重写)
@Override
public void eat() {
    super.eat();   // 调用父类版本
    // 子类自己的逻辑
}

重写规则

  • 方法签名必须相同(方法名 + 参数列表)
  • 返回类型可以是被重写方法的返回类型的子类(协变返回类型)
  • 访问权限不能更严格(如父类 protected → 子类 private ❌)
  • 抛出的异常不能更宽泛

9.5 Object 类

所有类的根父类,常用方法:

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

    // toString:返回对象的字符串表示
    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }

    // equals:比较对象内容是否相等
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && Objects.equals(name, user.name);
    }

    // hashCode:与 equals 保持一致,用于 HashMap 等集合
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

equals 与 hashCode 约定

  • 如果两个对象 equals 相等,它们的 hashCode 必须相等
  • 如果两个对象 hashCode 相等,它们 equals 不一定相等(哈希冲突)
  • 重写 equals必须重写 hashCode

10. 异常处理

10.1 异常体系

Throwable(可抛出的)
├── Error(JVM 严重错误,程序无法处理)
│   ├── OutOfMemoryError
│   └── StackOverflowError
└── Exception(程序可处理的异常)
    ├── RuntimeException(非检查异常,可不处理)
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   ├── IndexOutOfBoundsException
    │   └── ArithmeticException
    └── 检查异常(必须 try-catch 或 throws)
        ├── IOException
        ├── SQLException
        ├── FileNotFoundException
        └── ClassNotFoundException

10.2 try-catch-finally

try {
    // 可能发生异常的代码
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 捕获特定异常
    System.err.println("算术异常: " + e.getMessage());
} catch (Exception e) {
    // 捕获通用异常(从具体到通用)
    System.err.println("其他异常: " + e);
} finally {
    // 无论是否异常都执行(释放资源)
    System.out.println("finally 始终执行");
}

10.3 try-with-resources(JDK 7+)

自动关闭实现了 AutoCloseable 接口的资源。

// 传统方式(需要手动 close)
BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("a.txt"));
    System.out.println(br.readLine());
} finally {
    if (br != null) br.close();
}

// try-with-resources(自动关闭)
try (BufferedReader br = new BufferedReader(new FileReader("a.txt"))) {
    System.out.println(br.readLine());
}
// 资源自动按逆序调用 close()

10.4 throws 与 throw

// throws:声明方法可能抛出的异常
public void readFile(String path) throws FileNotFoundException, IOException {
    if (path == null) {
        throw new IllegalArgumentException("路径不能为空");  // throw:主动抛出异常
    }
    Files.readAllLines(Paths.get(path));
}

// 调用方必须处理(检查异常)
try {
    readFile("test.txt");
} catch (IOException e) {
    log.error("文件读取失败", e);
}

10.5 自定义异常

// 自定义运行时异常(不强制处理)
public class BusinessException extends RuntimeException {
    private final int code;

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }

    public int getCode() {
        return code;
    }
}

// 使用
throw new BusinessException(400, "参数不合法");

10.6 最佳实践

// ❌ 坏做法:吞异常
try { ... } catch (Exception e) {}

// ❌ 坏做法:只打印堆栈(生产环境不可用)
try { ... } catch (Exception e) { e.printStackTrace(); }

// ✅ 好做法:打日志 + 包装抛出
try { ... } catch (IOException e) {
    log.error("读取文件失败", e);
    throw new BusinessException("文件读取失败", e);
}

// ✅ 好做法:精细化异常
try { ... }
catch (FileNotFoundException e) {
    log.warn("文件不存在,使用默认配置");
} catch (IOException e) {
    log.error("IO 异常", e);
    throw new BusinessException("系统异常", e);
}

11. 常用工具类

11.1 Objects

Objects.equals(a, b);              // 安全的 equals(避免 NPE)
Objects.deepEquals(arr1, arr2);    // 深度比较(数组)
Objects.hash(name, age);           // 生成 hash 值
Objects.requireNonNull(obj);       // 非空检查,null 时抛 NPE
Objects.requireNonNull(obj, "obj 不能为空"); // 带错误消息
Objects.isNull(obj);               // 判断是否为 null
Objects.nonNull(obj);              // 判断是否非 null
Objects.toString(obj, "default");  // 安全的 toString

11.2 StringJoiner(JDK 8+)

StringJoiner sj = new StringJoiner(", ", "[", "]");
sj.add("A").add("B").add("C");
System.out.println(sj);  // [A, B, C]

11.3 Optional(JDK 8+)

优雅地处理空值,避免 NPE。

// 创建 Optional
Optional<String> empty = Optional.empty();
Optional<String> opt = Optional.of("Hello");        // value 不能为 null
Optional<String> nullable = Optional.ofNullable(null);  // value 可以为 null

// 使用
String result = nullable
        .map(String::toUpperCase)
        .filter(s -> s.length() > 3)
        .orElse("default");

11.4 Collections

List<String> list = new ArrayList<>();

Collections.sort(list);               // 排序
Collections.reverse(list);            // 反转
Collections.shuffle(list);            // 打乱
Collections.fill(list, "A");          // 填充
Collections.addAll(list, "a", "b");   // 批量添加
Collections.max(list);                // 最大值
Collections.min(list);                // 最小值
Collections.frequency(list, "a");     // 出现次数
Collections.swap(list, 0, 1);        // 交换位置

// 包装成线程安全集合
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
Map<String, String> safeMap = Collections.synchronizedMap(new HashMap<>());

11.5 Arrays

int[] arr = {3, 1, 4, 1, 5};

Arrays.toString(arr);                 // 转字符串
Arrays.sort(arr);                     // 排序
Arrays.binarySearch(arr, 4);          // 二分查找
Arrays.copyOf(arr, 10);               // 复制并扩容
Arrays.equals(arr1, arr2);            // 比较
Arrays.fill(arr, 0);                  // 填充
Arrays.asList("a", "b", "c");         // 数组转 List

// 并行排序(JDK 8+,多线程)
Arrays.parallelSort(arr);

11.6 Math

Math.abs(-10);                // 绝对值
Math.max(3, 5);               // 最大值
Math.min(3, 5);               // 最小值
Math.pow(2, 10);              // 幂运算:2^10 = 1024
Math.sqrt(16);                // 平方根
Math.random();                // [0.0, 1.0) 随机数
Math.round(3.6);              // 四舍五入:4
Math.floor(3.6);              // 向下取整:3
Math.ceil(3.2);               // 向上取整:4

11.7 System

System.out.println("输出");          // 标准输出
System.err.println("错误");          // 错误输出
System.in.read();                    // 标准输入
System.currentTimeMillis();          // 当前时间戳(毫秒)
System.nanoTime();                   // 高精度时间(纳秒)
System.arraycopy(src, 0, dest, 0, 10); // 数组拷贝
System.gc();                         // 建议 GC 执行
System.exit(0);                      // 退出 JVM
System.getProperty("java.version");  // 获取系统属性

11.8 Date / LocalDateTime

// 旧 API(JDK 1.0)
Date date = new Date();
System.out.println(date.getTime());  // 时间戳

// 新 API(JDK 8+,推荐使用)
LocalDate today = LocalDate.now();                           // 2026-07-04
LocalTime now = LocalTime.now();                              // 14:30:00
LocalDateTime dt = LocalDateTime.now();                       // 2026-07-04T14:30:00

// 格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(dt.format(formatter));                     // 2026-07-04 14:30:00

// 解析
LocalDateTime parsed = LocalDateTime.parse("2026-07-04 14:30:00", formatter);

// 计算
LocalDateTime tomorrow = dt.plusDays(1);
LocalDateTime lastMonth = dt.minusMonths(1);

12. 集合框架

12.1 集合体系总览

Collection(单列集合)
├── List(有序、可重复)
│   ├── ArrayList     底层数组,查快改快,增删慢
│   ├── LinkedList    底层双向链表,增删快,查慢
│   └── Vector        线程安全,已过时
├── Set(无序、不可重复)
│   ├── HashSet       哈希表,无序,O(1)
│   ├── LinkedHashSet 哈希表+链表,保持插入顺序
│   └── TreeSet       红黑树,排序,O(log n)
└── Queue(队列)
    └── LinkedList    双端队列/栈

Map(双列集合,键值对)
├── HashMap         数组+链表+红黑树,最常用
├── LinkedHashMap   保持插入/访问顺序
├── TreeMap         按键排序
├── ConcurrentHashMap 线程安全
└── Hashtable       全表锁,已过时

12.2 List

// ArrayList(默认选择)
List<String> list = new ArrayList<>();

// 增删改查
list.add("A");              // 尾部添加
list.add(0, "B");           // 指定位置插入
list.addAll(Arrays.asList("C", "D")); // 批量添加
list.get(0);                // 获取
list.set(0, "X");           // 修改
list.remove(0);             // 按索引删除
list.remove("A");           // 按对象删除
list.clear();               // 清空

// 查询
list.size();                // 长度
list.isEmpty();             // 是否为空
list.contains("A");         // 是否包含
list.indexOf("A");          // 首次出现位置
list.lastIndexOf("A");      // 最后出现位置
list.subList(0, 2);         // 截取 [0,2)

// 遍历
for (String s : list) { }
list.forEach(System.out::println);
Iterator<String> it = list.iterator();
while (it.hasNext()) { System.out.println(it.next()); }

// ArrayList 扩容机制
// 默认容量 16,扩容 1.5 倍(旧容量 + 旧容量 >> 1)
// 可用确保容量避免频繁扩容:new ArrayList<>(10000)

ArrayList vs LinkedList 选择

场景 推荐
随机访问多(get(i) ArrayList
尾部增删多 ArrayList
头部/中间频繁增删 LinkedList
不确定 ArrayList(大多数情况都选它)

12.3 Set

// HashSet
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("A");        // 重复元素不添加

set.contains("A");   // true
set.remove("A");
set.size();

// TreeSet(自动排序)
Set<Integer> sortedSet = new TreeSet<>();
sortedSet.add(3);
sortedSet.add(1);
sortedSet.add(2);
// 遍历结果:1, 2, 3(自动升序)

// 去重原理
// 1. 先比较 hashCode()
// 2. 如果 hashCode 相同,再比较 equals()
// 3. 都相同则认为是重复元素

12.4 Map ⭐

// HashMap(最常用)
Map<String, Integer> map = new HashMap<>();

// 增删改查
map.put("Alice", 90);                  // 添加/修改
map.putIfAbsent("Bob", 85);            // 不存在时才添加
map.get("Alice");                      // 获取,不存在返回 null
map.getOrDefault("Tom", 0);            // 获取,不存在返回默认值
map.remove("Alice");                   // 删除
map.containsKey("Alice");              // 是否包含 key
map.containsValue(85);                 // 是否包含 value
map.size();                            // 大小

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

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

// 方式3:forEach(JDK 8+)
map.forEach((k, v) -> System.out.println(k + ": " + v));

HashMap JDK 8+ 核心参数

参数
默认初始容量 16
负载因子 0.75
扩容倍数 2 倍(16 → 32 → 64)
树化阈值 链表长度 ≥ 8 数组容量 ≥ 64
退树化阈值 链表长度 ≤ 6

put() 执行流程

1. 计算 key.hashCode() → 扰动(高16位异或低16位)→ hash
2. 计算桶索引:index = (table.length - 1) & hash
3. 桶空 → 直接创建节点
4. 桶不空:
   a. 检查第一个节点 key 是否匹配 → 是则替换值
   b. 是红黑树 → 树插入
   c. 是链表 → 遍历
      - 找到匹配 key → 替换值
      - 找不到 → 尾插新节点
      - 链表长度 ≥ 8 → 转为红黑树
5. size > threshold → 扩容为 2 倍(rehash)

负载因子 0.75 的原因:时间和空间的平衡。0.5 空间浪费多,1.0 冲突多影响性能。

12.5 线程安全集合

// 方式1:并发集合(推荐)
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();  // 并发 Map
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();   // 读多写少 List
BlockingQueue<String> queue = new LinkedBlockingQueue<>();          // 阻塞队列

// 方式2:包装成同步集合
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
Map<String, String> safeMap = Collections.synchronizedMap(new HashMap<>());

13. 泛型

13.1 为什么需要泛型?

// ❌ 不使用泛型:需要手动强转,运行时可能 ClassCastException
List list = new ArrayList();
list.add("Hello");
list.add(123);
String s = (String) list.get(0);  // 没问题
String s2 = (String) list.get(1); // 运行时异常!

// ✅ 使用泛型:编译期类型检查,无需强转
List<String> list2 = new ArrayList<>();
list2.add("Hello");
// list2.add(123);  // 编译报错
String s3 = list2.get(0);  // 无需强转

13.2 泛型类

public class Box<T> {           // T:类型参数
    private T item;

    public void set(T item) {
        this.item = item;
    }

    public T get() {
        return item;
    }
}

Box<String> box = new Box<>();  // 钻石运算符 <>
box.set("Hello");
String value = box.get();

13.3 泛型方法

public class Utils {
    // 泛型方法:<T> 声明在返回类型之前
    public static <T> T getLast(List<T> list) {
        return list.get(list.size() - 1);
    }

    // 多个类型参数
    public static <K, V> void printEntry(K key, V value) {
        System.out.println(key + "=" + value);
    }
}

13.4 类型擦除

编译后泛型信息被擦除,替换为 Object 或边界类型。

// 编译前
List<String> list = new ArrayList<>();
String s = list.get(0);

// 编译后(类型擦除)
List list = new ArrayList();
String s = (String) list.get(0);  // 自动插入强转

// 验证擦除
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
a.getClass() == b.getClass();  // true(都是 ArrayList.class)

13.5 通配符与 PECS 原则

// <? extends T>:上界通配符(读取安全,写入不安全)
List<? extends Number> list = new ArrayList<Integer>();
Number n = list.get(0);   // ✅ 可读(用父类型接收)
// list.add(1);           // ❌ 不可写

// <? super T>:下界通配符(写入安全,读取不安全)
List<? super Integer> list = new ArrayList<Number>();
list.add(10);             // ✅ 可写
// Integer n = list.get(0); // ❌ 不可读(只能读 Object)

// <?>:无界通配符(只读)
List<?> list = new ArrayList<String>();
Object o = list.get(0);   // ✅ 只能读 Object
// list.add("a");         // ❌ 不可写(null 除外)

PECS 口诀Producer Extends,Consumer Super

  • 从集合中取数据 → 使用 extends(生产者)
  • 向集合中存数据 → 使用 super(消费者)
// 复制方法
public static void copy(List<? extends Number> src, List<? super Number> dest) {
    for (Number n : src) {   // src 是生产者 → extends
        dest.add(n);         // dest 是消费者 → super
    }
}

13.6 泛型限制(常见坑)

// ❌ 不能用基本类型
List<int> list;          // 编译报错
List<Integer> list;      // 必须用包装类

// ❌ 不能创建泛型数组
T[] arr = new T[10];     // 编译报错

// ❌ 不能 instanceof 泛型类型
list instanceof List<String>;  // 擦除后就是 List

// ❌ 静态成员不能使用泛型
public class Box<T> {
    private static T t;  // 编译报错
}

14. IO 流

14.1 核心分类

字节流(二进制文件)          字符流(文本文件)
├── InputStream(读)        ├── Reader(读)
│   ├── FileInputStream     │   ├── FileReader
│   ├── BufferedInputStream │   ├── BufferedReader
│   └── ObjectInputStream   │   └── InputStreamReader
├── OutputStream(写)       ├── Writer(写)
│   ├── FileOutputStream    │   ├── FileWriter
│   ├── BufferedOutputStream│   ├── BufferedWriter
│   └── ObjectOutputStream  │   └── OutputStreamWriter
分类 字节流 字符流
输入 InputStream Reader
输出 OutputStream Writer
缓冲 BufferedInputStream BufferedReader
文件 FileInputStream FileReader

14.2 文件操作

// 读文本文件(字符流)
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}  // try-with-resources 自动关闭

// 写文本文件(字符流)
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
    bw.write("Hello, World!");
    bw.newLine();
    bw.write("第二行");
}

// 复制二进制文件(字节流)
try (FileInputStream fis = new FileInputStream("source.jpg");
     FileOutputStream fos = new FileOutputStream("dest.jpg")) {
    byte[] buffer = new byte[8192];
    int len;
    while ((len = fis.read(buffer)) != -1) {
        fos.write(buffer, 0, len);
    }
}

14.3 NIO(JDK 7+,推荐)

Path path = Paths.get("test.txt");

// 读取全部内容
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);

// 写入文件
Files.write(Paths.get("out.txt"), "内容".getBytes());

// 文件复制
Files.copy(Paths.get("src.txt"), Paths.get("dest.txt"), StandardCopyOption.REPLACE_EXISTING);

// 创建/删除
Files.createFile(Paths.get("new.txt"));
Files.createDirectories(Paths.get("a/b/c"));  // 创建多层目录
Files.deleteIfExists(Paths.get("old.txt"));

// 判断
Files.exists(path);
Files.isDirectory(path);
Files.isRegularFile(path);

14.4 选择原则

  • 文本文件 → 字符流(Reader/Writer
  • 图片/视频/音频/二进制 → 字节流(InputStream/OutputStream
  • 大文件 → 缓冲流(BufferedXxx)或 NIO
  • 需要随机读写RandomAccessFile

15. 注解与反射

15.1 注解

元注解

@Retention(RetentionPolicy.RUNTIME)    // 生命周期:SOURCE / CLASS / RUNTIME
@Target(ElementType.METHOD)            // 使用位置:TYPE / METHOD / FIELD 等
@Documented                            // 生成 Javadoc
@Inherited                             // 子类可继承
@Repeatable(MyTags.class)              // 可重复使用(JDK 8+)
public @interface MyAnnotation {
    String value() default "";
    int count() default 1;
}

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
    Level level() default Level.INFO;

    enum Level {
        INFO, WARN, ERROR
    }
}

// 使用
@Log(value = "用户登录", level = Log.Level.INFO)
public void login() { }

15.2 反射

获取 Class 的 3 种方式

Class<?> clazz1 = Class.forName("java.lang.String");  // 方式1:全类名(最灵活)
Class<?> clazz2 = String.class;                        // 方式2:类名.class
Class<?> clazz3 = "Hello".getClass();                  // 方式3:对象.getClass()

反射核心 API

Class<?> clazz = User.class;

// 创建实例
User user = clazz.getDeclaredConstructor().newInstance();

// 获取方法
Method method = clazz.getMethod("setName", String.class);
method.invoke(user, "Alice");

// 获取字段(包括 private)
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);     // 暴力破解 private
field.set(user, "Bob");
String name = (String) field.get(user);

// 获取注解
Autowired autowired = method.getAnnotation(Autowired.class);

反射性能优化

// ❌ 坏做法:每次都查(慢 200 倍)
for (int i = 0; i < 10000; i++) {
    Method m = clazz.getMethod("getName");
    m.invoke(user);
}

// ✅ 好做法:缓存 Method
Method method = clazz.getMethod("getName");
method.setAccessible(true);    // 关闭安全检查
for (int i = 0; i < 10000; i++) {
    method.invoke(user);
}

16. Lambda 表达式与 Stream

16.1 函数式接口

只有一个抽象方法的接口(可用 @FunctionalInterface 标注)。

接口 方法 用途
Consumer<T> void accept(T) 消费数据
Supplier<T> T get() 提供数据
Function<T,R> R apply(T) 类型转换
Predicate<T> boolean test(T) 条件判断
Consumer<String> consumer = s -> System.out.println(s);
Supplier<String> supplier = () -> "Hello";
Function<String, Integer> function = s -> s.length();
Predicate<String> predicate = s -> s.length() > 3;

16.2 Lambda 语法

// 完整语法
(参数列表) -> { 方法体 }

// 示例
// 无参数
Runnable task = () -> System.out.println("Hello");

// 一个参数
Consumer<String> c = s -> System.out.println(s);

// 多个参数
Comparator<Integer> comp = (a, b) -> a - b;

// 方法体多行
Function<String, Integer> f = (s) -> {
    int len = s.length();
    System.out.println("长度:" + len);
    return len;
};

16.3 方法引用(4种)

// 1. 静态方法引用:类名::静态方法
list.stream().map(Math::abs);

// 2. 实例方法引用(特定对象):对象::实例方法
list.forEach(System.out::println);

// 3. 实例方法引用(任意对象):类名::实例方法
list.stream().map(String::toUpperCase);

// 4. 构造器引用:类名::new
Supplier<User> supplier = User::new;

16.4 Stream API

List<String> list = Arrays.asList("apple", "banana", "cherry", "date");

// 中间操作(惰性求值,不触发执行)
Stream<String> stream = list.stream()
    .filter(s -> s.length() > 4)         // 过滤
    .map(String::toUpperCase)            // 转换
    .sorted()                            // 排序
    .distinct()                          // 去重
    .limit(3)                            // 取前3个
    .skip(1);                            // 跳过前1个

// 终端操作(触发执行)
List<String> result = stream.collect(Collectors.toList());

// 常用终端操作
list.stream().collect(Collectors.toList());                     // 收集为 List
list.stream().collect(Collectors.toSet());                      // 收集为 Set
list.stream().collect(Collectors.joining(", "));               // 拼接字符串
list.stream().collect(Collectors.groupingBy(s -> s.length()));  // 分组
list.stream().collect(Collectors.partitioningBy(s -> s.length() > 4)); // 分区

list.stream().count();                                          // 计数
list.stream().reduce(0, Integer::sum);                          // 规约(求和)
list.stream().findFirst();                                      // 第一个元素
list.stream().findAny();                                        // 任意元素
list.stream().anyMatch(s -> s.length() > 5);                    // 有一个匹配?
list.stream().allMatch(s -> s.length() > 0);                    // 全部匹配?
list.stream().noneMatch(s -> s.isEmpty());                      // 没有匹配?
list.stream().forEach(System.out::println);                     // 遍历

16.5 注意点

  • 惰性求值:中间操作不执行,必须有终端操作才触发执行
  • 流一次性Stream 只能用一次,用完抛 IllegalStateException
  • 并行流parallelStream() 使用 ForkJoinPool,大集合性能好,但注意线程安全

17. 并发编程

17.1 线程创建方式

// 方式1:继承 Thread(不推荐)
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}
new MyThread().start();

// 方式2:实现 Runnable(推荐)
Runnable task = () -> System.out.println("线程执行");
new Thread(task).start();

// 方式3:Callable + Future(有返回值)
FutureTask<String> ft = new FutureTask<>(() -> {
    Thread.sleep(1000);
    return "执行结果";
});
new Thread(ft).start();
String result = ft.get();  // 阻塞等待结果

17.2 线程状态

NEW(新建) → RUNNABLE(就绪/运行) → BLOCKED(阻塞)
                 ↓                        ↓
           WAITING(等待)           TIMED_WAITING(超时等待)
                 ↓
           TERMINATED(终止)
Thread.State[] states = Thread.State.values();
// NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED

17.3 synchronized

// 锁对象实例
public synchronized void add() { count++; }           // 锁 this
public void add() { synchronized(this) { count++; } } // 等价

// 锁类
public static synchronized void staticAdd() { ... }   // 锁 Class 对象

锁升级过程(JDK 8):无锁 → 偏向锁 → 轻量级锁(CAS自旋)→ 重量级锁(互斥)

17.4 volatile

// volatile 保证可见性和禁止指令重排,但不保证原子性
public class Running {
    private volatile boolean running = true;  // 状态标志

    public void run() {
        while (running) {  // 读取 volatile 变量,确保看到其他线程的修改
            // ...
        }
    }

    public void stop() {
        running = false;
    }
}

17.5 ReentrantLock

private final ReentrantLock lock = new ReentrantLock();
private int count = 0;

public void increment() {
    lock.lock();      // 加锁
    try {
        count++;
    } finally {
        lock.unlock();  // 务必在 finally 中释放
    }
}

17.6 线程池(ThreadPoolExecutor)

// 手动创建线程池(推荐,阿里巴巴规约禁止使用 Executors)
ExecutorService pool = new ThreadPoolExecutor(
    2,                 // corePoolSize:核心线程数
    5,                 // maximumPoolSize:最大线程数
    60L,               // keepAliveTime:空闲线程存活时间
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),  // workQueue:阻塞队列
    Executors.defaultThreadFactory(), // threadFactory
    new ThreadPoolExecutor.AbortPolicy()  // handler:拒绝策略
);

// 提交任务
pool.execute(() -> System.out.println("无返回值"));
Future<String> future = pool.submit(() -> "有返回值");
String result = future.get();

// 关闭
pool.shutdown();                    // 等待已提交任务完成
// pool.shutdownNow();              // 立即关闭

拒绝策略

策略 行为
AbortPolicy(默认) 抛出 RejectedExecutionException
CallerRunsPolicy 调用者线程执行
DiscardPolicy 静默丢弃
DiscardOldestPolicy 丢弃队列头部旧任务

17.7 JUC 常用类

// CountDownLatch:等待多个任务完成
CountDownLatch latch = new CountDownLatch(3);
// 每个子任务结束时:latch.countDown();
// 主线程等待:latch.await();

// CyclicBarrier:多个线程互相等待
CyclicBarrier barrier = new CyclicBarrier(3);
// 每个线程执行到某个点时:barrier.await();

// Semaphore:限流
Semaphore semaphore = new Semaphore(5);  // 最多5个并发
semaphore.acquire();  // 获取许可
// ... 业务逻辑 ...
semaphore.release();  // 释放许可

18. JVM 内存模型

18.1 运行时数据区

JVM 内存结构:

┌──────────────────────────────────────────────┐
│              堆(Heap)                       │
│  ┌──────────────┬────────────┐               │
│  │   新生代      │  老年代    │               │
│  │ Eden│S0│S1  │            │               │
│  └──────────────┴────────────┘               │
│  所有线程共享,存放对象实例                    │
├──────────────────────────────────────────────┤
│              方法区(Metaspace)              │
│  类的元数据、静态变量、常量池                  │
│  所有线程共享                                │
├──────────────────────────────────────────────┤
│          虚拟机栈(VM Stack)                 │
│  局部变量表、操作数栈、方法调用栈              │
│  每个线程独有                                │
├──────────────────────────────────────────────┤
│          本地方法栈(Native Stack)           │
│  native 方法调用                            │
├──────────────────────────────────────────────┤
│          程序计数器(PC Register)            │
│  当前线程执行的字节码行号                      │
└──────────────────────────────────────────────┘

18.2 垃圾回收(GC)

判断对象是否存活:可达性分析(GC Roots)

GC Roots 包括

  • 栈帧中局部变量表引用的对象
  • 静态变量引用的对象
  • JNI(本地方法)引用的对象

常见垃圾回收器

回收器 适用版本 特点
G1 JDK 9+ 默认 分区回收,可预测停顿
ZGC JDK 11+ 超低延迟(<10ms),染色指针
CMS JDK 8 常用 并发回收,减少停顿(已过时)

18.3 类加载机制

加载过程:加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载

双亲委派模型

Bootstrap ClassLoader(启动类加载器,加载 rt.jar)
        ↑ 委派
Extension ClassLoader(扩展类加载器,加载 lib/ext)
        ↑ 委派
Application ClassLoader(应用类加载器,加载 classpath)
        ↑ 委派
用户自定义 ClassLoader

双亲委派优势:避免同一个类被重复加载,保证核心类(如 java.lang.String)不被篡改。


19. 附录:速查表

集合选择指南

需求 推荐方案
有序列表,随机访问多 ArrayList
频繁头部/中间插入删除 LinkedList
去重,无序 HashSet
去重,排序 TreeSet
键值对,最常用 HashMap
键值对,保持插入顺序 LinkedHashMap
键值对,按键排序 TreeMap
并发 Map ConcurrentHashMap
并发 List CopyOnWriteArrayList

异常速查

异常 常见原因
NullPointerException 调用 null 对象的方法或属性
ArrayIndexOutOfBoundsException 数组越界
ClassCastException 类型转换失败
IllegalArgumentException 非法参数
ArithmeticException 算术异常(除零)
NumberFormatException 数字格式错误

线程池参数速查

new ThreadPoolExecutor(
    corePoolSize,      // 核心线程数
    maximumPoolSize,   // 最大线程数
    keepAliveTime,     // 非核心线程空闲存活时间
    unit,              // 时间单位
    workQueue,         // 阻塞队列(如 LinkedBlockingQueue)
    threadFactory,     // 线程工厂(可自定义线程名)
    handler            // 拒绝策略
);

JVM 参数速查

参数 说明 示例
-Xms 初始堆大小 -Xms256m
-Xmx 最大堆大小 -Xmx1024m
-Xss 栈大小 -Xss512k
-XX:MetaspaceSize 元空间大小 -XX:MetaspaceSize=128m
-XX:+PrintGCDetails 打印 GC 详情

知识点模块速查

模块 核心要点
数据类型 8 种基本类型,引用类型存地址,包装类有缓存
面向对象 封装/继承/多态,this/super/static/final
集合 ArrayList 查快,HashMap 最常用,ConcurrentHashMap 并发
泛型 编译期检查 + 运行期擦除,PECS 原则
反射 运行时获取类元数据,setAccessible(true) 暴力破解
异常 运行时异常不强制处理,检查异常必须处理
IO 文本用字符流,二进制用字节流,大文件用 NIO
Lambda invokedynamic 动态生成,性能优于匿名内部类
Stream 惰性求值,流一次性,parallelStream() 并行
并发 synchronized 锁升级,volatile 可见性,线程池 7 参数
JVM 堆存对象,栈存引用,方法区存类元数据

推荐学习路径

  1. 基础:数据类型 → 流程控制 → 数组/字符串 → 面向对象
  2. 进阶:集合 → 泛型 → IO → 异常
  3. 高阶:反射/注解 → Lambda/Stream → 并发 → JVM
  4. 框架:Spring → MyBatis → Spring Boot

Comments | 0条评论