01.01 Java基础篇|语言基础与开发环境速成

01.01 Java基础篇|语言基础与开发环境速成

导读

  • 适用人群:Java 初学者或需要系统梳理基础知识的开发者
  • 学习目标:掌握 Java 语言核心语法、开发环境配置、常用 API 使用,为后续深入学习打下坚实基础
  • 阅读建议:建议配合实际编码练习,理解每个概念的实际应用场景

核心知识架构

开发环境与项目结构

JDK/JRE/JVM 关系深度解析

三者的层次关系

复制代码
┌─────────────────────────────────────┐
│           JDK (Java Development Kit)│
│  ┌────────────────────────────────┐ │
│  │     JRE (Java Runtime Env)     │ │
│  │  ┌──────────────────────────┐  │ │
│  │  │   JVM (Java Virtual      │  │ │
│  │  │        Machine)          │  │ │
│  │  └──────────────────────────┘  │ │
│  │  + 核心类库 (rt.jar等)         │ │
│  └────────────────────────────────┘ │
│  + javac (编译器)                   │
│  + javap (反汇编器)                 │
│  + jconsole (监控工具)              │
│  + jlink (模块化打包)               │
└─────────────────────────────────────┘

源码级理解

  • JVM:负责字节码解释执行、内存管理(堆、栈、方法区)、GC 等
  • JRE :JVM + 核心类库(java.langjava.util 等),提供运行时环境
  • JDK :JRE + 开发工具(javacjavapjconsole 等),用于开发与调试

生产环境使用 jlink (JDK 9+)优化

jlink 是 Java Platform Module System (JPMS) 的一部分,允许创建只包含所需模块的定制化JRE。

bash 复制代码
# 列出所有可用模块
java --list-modules
# 或使用 jlink 查看
jlink --list-plugins
# 基本语法
jlink --add-modules <模块列表> --output <输出目录> --module-path <模块路径>
# 示例:创建包含最小模块的运行时
jlink \
  --module-path $JAVA_HOME/jmods \
  --add-modules java.base \
  --output myjre

# 压缩选项
--compress=0    # 不压缩(默认)
--compress=1    # 常量字符串共享
--compress=2    # 所有资源的 ZIP 压缩

# 优化选项
--strip-debug      # 移除调试信息
--no-header-files  # 移除头文件
--no-man-pages     # 移除手册页

# 绑定服务
--bind-services    # 链接服务提供者模块

# 生成启动脚本
--launcher <name>=<module>/<main-class>
安装与配置最佳实践

多版本管理(推荐使用 SDKMAN!)

bash 复制代码
# 安装 SDKMAN!
curl -s "https://get.sdkman.io" | bash

# 安装多个 JDK 版本
sdk install java 17.0.8-tem
sdk install java 21.0.1-tem

# 切换版本
sdk use java 21.0.1-tem

# 查看当前版本
java -version

环境变量配置(跨平台)

bash 复制代码
# Linux/macOS (.bashrc 或 .zshrc)
export JAVA_HOME=$(/usr/libexec/java_home -v 17)  # macOS
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk     # Linux
export PATH=$JAVA_HOME/bin:$PATH

# Windows (PowerShell)
[Environment]::SetEnvironmentVariable("JAVA_HOME", "D:\Java\jdk-17", "User")
$env:Path = "$env:JAVA_HOME\bin;$env:Path"
Maven 项目结构详解
复制代码
demo/
├─ pom.xml                    # 项目配置、依赖管理
├─ src/
│  ├─ main/
│  │  ├─ java/                # 源代码(包结构:com/company/module/)
│  │  │  └─ com/example/App.java
│  │  └─ resources/           # 资源文件(配置文件、模板等)
│  │     ├─ application.yml
│  │     └─ logback.xml
│  └─ test/
│     ├─ java/                # 测试代码
│     └─ resources/           # 测试资源
├─ target/                    # 编译输出(.gitignore)
└─ .gitignore

pom.xml 核心配置示例

xml 复制代码
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <!-- 依赖声明 -->
    </dependencies>
</project>

语言语法速览

数据类型深度解析

基本类型与包装类型对比

基本类型 大小 包装类型 默认值 缓存范围
byte 1字节 Byte 0 -128~127
short 2字节 Short 0 -128~127
int 4字节 Integer 0 -128~127
long 8字节 Long 0L -128~127
float 4字节 Float 0.0f
double 8字节 Double 0.0d
char 2字节 Character '\u0000' 0~127
boolean 1位 Boolean false true/false

源码分析:Integer 缓存机制

java 复制代码
// java.lang.Integer 源码片段
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

// 默认缓存范围:-128 到 127
// 可通过 -XX:AutoBoxCacheMax=<size> 调整上限

实际应用示例

java 复制代码
Integer a = 100;  // 自动装箱,使用缓存
Integer b = 100;
System.out.println(a == b);  // true(同一对象)

Integer c = 200;  // 超出缓存范围
Integer d = 200;
System.out.println(c == d);  // false(不同对象)
System.out.println(c.equals(d));  // true(值相等)
字符串不可变性源码分析

String 类的核心设计

java 复制代码
// java.lang.String 源码关键部分
public final class String implements java.io.Serializable, 
                                    Comparable<String>, 
                                    CharSequence {
    private final char[] value;  // JDK 8 及之前
    // JDK 9+ 改为 byte[] + coder (Latin1/UTF16)
    
    private final int hash;  // 缓存 hashCode
    
    // 构造器确保 value 数组不可变
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
}

不可变性的优势

  1. 线程安全:无需同步即可多线程共享
  2. 缓存优化:字符串常量池(String Pool)复用相同字符串
  3. 安全性:作为参数传递时不会被修改(如 ClassLoader、网络连接)

StringBuilder vs StringBuffer 源码对比

java 复制代码
// StringBuilder (非线程安全,性能更高)
public final class StringBuilder extends AbstractStringBuilder {
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    // 方法未加 synchronized
}

// StringBuffer (线程安全,性能较低)
public final class StringBuffer extends AbstractStringBuilder {
    @Override
    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }
    // 所有方法都加 synchronized
}

性能测试示例

java 复制代码
// 错误示例:频繁字符串拼接
String result = "";
for (int i = 0; i < 10000; i++) {
    result += i;  // 每次创建新 String 对象,O(n²) 复杂度
}

// 正确示例:使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);  // O(n) 复杂度
}
String result = sb.toString();
Switch 表达式演进(JDK 14+)

传统 switch 语句

java 复制代码
int day = 1;
String dayName;
switch (day) {
    case 1:
        dayName = "Monday";
        break;
    case 2:
        dayName = "Tuesday";
        break;
    default:
        dayName = "Unknown";
}

Switch 表达式(JDK 14+)

java 复制代码
String dayName = switch (day) {
    case 1 -> "Monday";
    case 2 -> "Tuesday";
    case 3, 4, 5 -> "Weekday";  // 多值匹配
    default -> "Unknown";
};

// 使用 yield 处理复杂逻辑
int result = switch (day) {
    case 1, 2, 3 -> {
        System.out.println("Processing...");
        yield day * 2;  // yield 返回值
    }
    default -> day;
};

模式匹配(JDK 17+ 预览,JDK 21 正式)

java 复制代码
Object obj = "Hello";
String result = switch (obj) {
    case String s when s.length() > 5 -> "Long string: " + s;
    case String s -> "Short string: " + s;
    case Integer i -> "Number: " + i;
    case null -> "Null value";
    default -> "Unknown";
};

常用 API 深度解析

时间日期 API(java.time 包)

为什么需要新的时间 API?

  • DateCalendar 设计缺陷:可变性、线程不安全、API 混乱
  • java.time 包(JDK 8+)提供不可变、线程安全的时间处理

核心类层次

java 复制代码
// 本地日期时间(无时区)
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();

// 带时区的日期时间
ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));

// 时间戳(UTC)
Instant instant = Instant.now();

// 时间间隔
Duration duration = Duration.between(start, end);
Period period = Period.between(startDate, endDate);

实战示例:时区转换

java 复制代码
// 北京时间转纽约时间
LocalDateTime beijingTime = LocalDateTime.of(2024, 1, 1, 12, 0);
ZonedDateTime beijing = beijingTime.atZone(ZoneId.of("Asia/Shanghai"));
ZonedDateTime newYork = beijing.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(newYork);  // 2024-01-01T00:00-05:00[America/New_York]
异常体系源码分析

异常类层次结构

java 复制代码
Throwable (可抛出)
├── Error (错误,不应捕获)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── VirtualMachineError
└── Exception (异常,可处理)
    ├── RuntimeException (非检查异常)
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   └── IndexOutOfBoundsException
    └── 其他 Exception (检查异常)
        ├── IOException
        ├── SQLException
        └── ClassNotFoundException

try-with-resources 实现原理

java 复制代码
// 源代码
try (FileInputStream fis = new FileInputStream("file.txt")) {
    // 使用资源
}

// 编译器生成的等价代码
FileInputStream fis = new FileInputStream("file.txt");
Throwable primaryException = null;
try {
    // 使用资源
} catch (Throwable t) {
    primaryException = t;
    throw t;
} finally {
    if (fis != null) {
        if (primaryException != null) {
            try {
                fis.close();
            } catch (Throwable suppressedException) {
                primaryException.addSuppressed(suppressedException);
            }
        } else {
            fis.close();
        }
    }
}

自定义异常最佳实践

java 复制代码
// 业务异常基类
public class BizException extends RuntimeException {
    private final String errorCode;
    private final Object[] args;
    
    public BizException(String errorCode, String message, Object... args) {
        super(message);
        this.errorCode = errorCode;
        this.args = args;
    }
    
    // 提供错误码,便于前端展示
    public String getErrorCode() {
        return errorCode;
    }
}

// 使用示例
if (user == null) {
    throw new BizException("USER_NOT_FOUND", "用户不存在: %s", userId);
}


源码讲解|深度剖析

示例 1:命令行参数解析与配置加载(生产级实现)

基础版本

java 复制代码
public final class App {
    public static void main(String[] args) {
        String profile = args.length > 0 ? args[0] : "dev";
        System.out.printf("Profile = %s%n", profile);

        Properties props = new Properties();
        try (InputStream in = App.class.getResourceAsStream("/app-" + profile + ".properties")) {
            props.load(in);
            System.out.println("DB URL: " + props.getProperty("db.url"));
        } catch (IOException e) {
            throw new IllegalStateException("加载配置失败", e);
        }
    }
}

增强版本(支持环境变量覆盖、类型转换)

java 复制代码
public class ConfigLoader {
    private final Properties props = new Properties();
    
    public ConfigLoader(String profile) {
        loadFromClasspath(profile);
        overrideWithEnvVars();  // 环境变量优先级更高
    }
    
    private void loadFromClasspath(String profile) {
        String resource = "/app-" + profile + ".properties";
        try (InputStream in = getClass().getResourceAsStream(resource)) {
            if (in == null) {
                throw new IllegalStateException("配置文件不存在: " + resource);
            }
            props.load(in);
        } catch (IOException e) {
            throw new IllegalStateException("加载配置失败", e);
        }
    }
    
    private void overrideWithEnvVars() {
        // 环境变量格式:APP_DB_URL -> db.url
        props.stringPropertyNames().forEach(key -> {
            String envKey = "APP_" + key.replace(".", "_").toUpperCase();
            String envValue = System.getenv(envKey);
            if (envValue != null) {
                props.setProperty(key, envValue);
            }
        });
    }
    
    // 类型安全的配置读取
    public String getString(String key, String defaultValue) {
        return props.getProperty(key, defaultValue);
    }
    
    public int getInt(String key, int defaultValue) {
        String value = props.getProperty(key);
        return value != null ? Integer.parseInt(value) : defaultValue;
    }
    
    public boolean getBoolean(String key, boolean defaultValue) {
        String value = props.getProperty(key);
        return value != null ? Boolean.parseBoolean(value) : defaultValue;
    }
}

源码分析:Properties.load() 实现原理

java 复制代码
// java.util.Properties 源码片段
public synchronized void load(InputStream inStream) throws IOException {
    load(new InputStreamReader(inStream, "ISO-8859-1"));  // 注意编码
    // 实际使用建议:使用 loadFromXML() 或手动指定 UTF-8
}

// 推荐使用方式(JDK 9+)
Properties props = new Properties();
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    props.load(reader);
}

Spring Boot 读取 YAML 配置文件原理

Spring Boot 使用 YamlPropertiesFactoryBeanYamlMapFactoryBean 来解析 YAML 文件,底层依赖 SnakeYAML 库。

核心实现机制

java 复制代码
// Spring Boot 配置加载流程
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    private String dbUrl;
    private int dbPort;
    private List<String> servers;
    
    // getter/setter
}

// application.yml
app:
  db-url: jdbc:mysql://localhost:3306/test
  db-port: 3306
  servers:
    - server1.example.com
    - server2.example.com

Spring Boot YAML 加载源码分析

java 复制代码
// org.springframework.boot.env.YamlPropertySourceLoader
public class YamlPropertySourceLoader implements PropertySourceLoader {
    @Override
    public List<PropertySource<?>> load(String name, Resource resource) 
            throws IOException {
        if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
            throw new IllegalStateException("SnakeYAML not found");
        }
        
        Yaml yaml = createYaml();
        try (InputStream inputStream = resource.getInputStream()) {
            // 解析 YAML 为 Map
            List<Map<String, Object>> loaded = yaml.load(inputStream);
            return loaded.stream()
                .map(map -> new MapPropertySource(name, flattenMap(map)))
                .collect(Collectors.toList());
        }
    }
    
    // 扁平化嵌套 Map(app.db.url -> app.db.url)
    private Map<String, Object> flattenMap(Map<String, Object> source) {
        Map<String, Object> result = new LinkedHashMap<>();
        buildFlattenedMap(result, source, null);
        return result;
    }
}

实际应用示例

java 复制代码
// 方式1:使用 @ConfigurationProperties(推荐)
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppProperties {
    private Database database = new Database();
    private Redis redis = new Redis();
    
    @Data
    public static class Database {
        private String url;
        private String username;
        private String password;
    }
    
    @Data
    public static class Redis {
        private String host;
        private int port;
        private int timeout;
    }
}

// 方式2:使用 @Value 注解
@Component
public class AppConfig {
    @Value("${app.database.url}")
    private String dbUrl;
    
    @Value("${app.database.port:3306}")  // 默认值
    private int dbPort;
    
    @Value("${app.servers}")
    private List<String> servers;
}

// 方式3:使用 Environment
@Autowired
private Environment env;

public void loadConfig() {
    String dbUrl = env.getProperty("app.database.url");
    int dbPort = env.getProperty("app.database.port", Integer.class, 3306);
}

YAML vs Properties 对比

特性 YAML Properties
可读性 高(层次清晰) 中(扁平结构)
嵌套支持 原生支持 需用点号分隔
类型支持 丰富(List、Map) 字符串为主
注释 支持 支持
Spring Boot 默认支持 默认支持

多环境配置(Profile)

yaml 复制代码
# application.yml(默认配置)
app:
  database:
    url: jdbc:mysql://localhost:3306/dev

---
# application-prod.yml(生产环境)
spring:
  profiles: prod
app:
  database:
    url: jdbc:mysql://prod-server:3306/prod

配置优先级(从高到低)

  1. 命令行参数:--app.database.url=xxx
  2. 环境变量:APP_DATABASE_URL=xxx
  3. application-{profile}.yml
  4. application.yml
  5. 默认值

示例 2:不可变对象设计模式

基础 POJO

java 复制代码
public class User {
    private final String id;
    private final String name;
    private int age;  // 可变字段

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

    public String getDisplay() {
        return String.format("[%s] %s(%d)", id, name, age);
    }
}

完全不可变对象(推荐)

java 复制代码
// 使用 final 类 + final 字段,确保不可变
public final class ImmutableUser {
    private final String id;
    private final String name;
    private final int age;
    private final List<String> tags;  // 集合也需要不可变

    public ImmutableUser(String id, String name, int age, List<String> tags) {
        this.id = Objects.requireNonNull(id, "id不能为空");
        this.name = Objects.requireNonNull(name, "name不能为空");
        this.age = age;
        // 防御性拷贝,防止外部修改
        this.tags = Collections.unmodifiableList(new ArrayList<>(tags));
    }

    // 只提供 getter,不提供 setter
    public String getId() { return id; }
    public String getName() { return name; }
    public int getAge() { return age; }
    public List<String> getTags() { 
        // 返回不可变视图
        return tags; 
    }
    
    // 提供 withXxx 方法创建新对象(函数式风格)
    public ImmutableUser withAge(int newAge) {
        return new ImmutableUser(this.id, this.name, newAge, this.tags);
    }
}

使用 Record 类型(JDK 14+,更简洁)

java 复制代码
// Record 自动生成 final 字段、构造器、equals、hashCode、toString
public record UserRecord(String id, String name, int age, List<String> tags) {
    // 紧凑构造器,可添加验证逻辑
    public UserRecord {
        Objects.requireNonNull(id);
        Objects.requireNonNull(name);
        tags = List.copyOf(tags);  // 创建不可变副本
    }
    
    // 自定义方法
    public String getDisplay() {
        return String.format("[%s] %s(%d)", id, name, age);
    }
}

Builder 模式(复杂对象构造)

java 复制代码
public final class User {
    private final String id;
    private final String name;
    private final int age;
    private final String email;
    private final List<String> roles;
    
    private User(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.age = builder.age;
        this.email = builder.email;
        this.roles = Collections.unmodifiableList(builder.roles);
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static class Builder {
        private String id;
        private String name;
        private int age;
        private String email;
        private List<String> roles = new ArrayList<>();
        
        public Builder id(String id) {
            this.id = id;
            return this;
        }
        
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        
        public Builder email(String email) {
            this.email = email;
            return this;
        }
        
        public Builder addRole(String role) {
            this.roles.add(role);
            return this;
        }
        
        public User build() {
            // 构建时验证
            Objects.requireNonNull(id, "id不能为空");
            Objects.requireNonNull(name, "name不能为空");
            if (age < 0) {
                throw new IllegalArgumentException("age必须大于0");
            }
            return new User(this);
        }
    }
}

// 使用示例
User user = User.builder()
    .id("u001")
    .name("Alice")
    .age(25)
    .email("alice@example.com")
    .addRole("USER")
    .addRole("ADMIN")
    .build();


实战案例

案例 1:命令行 Todo 管理器(完整实现)

需求分析

  • 支持添加、完成、列出、删除任务
  • 数据持久化到 JSON 文件
  • 支持任务优先级和分类

完整实现代码

java 复制代码
// Task 实体类(使用 Record)
public record Task(String id, String description, boolean completed, 
                   Priority priority, LocalDateTime createdAt) {
    public enum Priority { LOW, MEDIUM, HIGH }
    
    public Task {
        Objects.requireNonNull(id);
        Objects.requireNonNull(description);
        if (description.isBlank()) {
            throw new IllegalArgumentException("描述不能为空");
        }
    }
    
    public Task complete() {
        return new Task(id, description, true, priority, createdAt);
    }
    
    public String format() {
        String status = completed ? "[✓]" : "[ ]";
        return String.format("%s %s (%s) - %s", 
            status, description, priority, 
            createdAt.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }
}

// 任务管理器
public class TodoManager {
    private final Path dataFile;
    private final ObjectMapper mapper;
    private List<Task> tasks;
    
    public TodoManager(Path dataFile) {
        this.dataFile = dataFile;
        this.mapper = new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        loadTasks();
    }
    
    private void loadTasks() {
        try {
            if (Files.exists(dataFile)) {
                String json = Files.readString(dataFile, StandardCharsets.UTF_8);
                tasks = mapper.readValue(json, 
                    mapper.getTypeFactory().constructCollectionType(List.class, Task.class));
            } else {
                tasks = new ArrayList<>();
            }
        } catch (IOException e) {
            throw new IllegalStateException("加载任务失败", e);
        }
    }
    
    private void saveTasks() {
        try {
            String json = mapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(tasks);
            Files.writeString(dataFile, json, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new IllegalStateException("保存任务失败", e);
        }
    }
    
    public void addTask(String description, Task.Priority priority) {
        String id = UUID.randomUUID().toString();
        Task task = new Task(id, description, false, priority, LocalDateTime.now());
        tasks.add(task);
        saveTasks();
        System.out.println("✓ 任务已添加: " + task.format());
    }
    
    public void completeTask(String id) {
        tasks = tasks.stream()
            .map(task -> task.id().equals(id) ? task.complete() : task)
            .collect(Collectors.toList());
        saveTasks();
        System.out.println("✓ 任务已完成: " + id);
    }
    
    public void listTasks(boolean showCompleted) {
        tasks.stream()
            .filter(task -> showCompleted || !task.completed())
            .forEach(task -> System.out.println(task.format()));
    }
    
    public void deleteTask(String id) {
        boolean removed = tasks.removeIf(task -> task.id().equals(id));
        if (removed) {
            saveTasks();
            System.out.println("✓ 任务已删除: " + id);
        } else {
            System.out.println("✗ 任务不存在: " + id);
        }
    }
}

// 主程序
public class TodoApp {
    public static void main(String[] args) {
        if (args.length == 0) {
            printUsage();
            return;
        }
        
        Path dataFile = Paths.get(System.getProperty("user.home"), ".todo.json");
        TodoManager manager = new TodoManager(dataFile);
        
        String command = args[0];
        switch (command) {
            case "add" -> {
                if (args.length < 2) {
                    System.err.println("错误: 请提供任务描述");
                    return;
                }
                String description = String.join(" ", 
                    Arrays.copyOfRange(args, 1, args.length));
                Task.Priority priority = args.length > 2 && 
                    args[args.length - 1].matches("LOW|MEDIUM|HIGH") 
                    ? Task.Priority.valueOf(args[args.length - 1])
                    : Task.Priority.MEDIUM;
                manager.addTask(description, priority);
            }
            case "done" -> {
                if (args.length < 2) {
                    System.err.println("错误: 请提供任务ID");
                    return;
                }
                manager.completeTask(args[1]);
            }
            case "list" -> {
                boolean showAll = args.length > 1 && args[1].equals("--all");
                manager.listTasks(showAll);
            }
            case "delete" -> {
                if (args.length < 2) {
                    System.err.println("错误: 请提供任务ID");
                    return;
                }
                manager.deleteTask(args[1]);
            }
            default -> {
                System.err.println("未知命令: " + command);
                printUsage();
            }
        }
    }
    
    private static void printUsage() {
        System.out.println("""
            用法: todo <command> [args]
            
            命令:
              add <description> [PRIORITY]  添加任务 (PRIORITY: LOW/MEDIUM/HIGH)
              done <id>                      完成任务
              list [--all]                   列出任务 (--all 显示已完成)
              delete <id>                    删除任务
            """);
    }
}

技术要点

  1. 使用 Record 简化不可变对象:自动生成 equals、hashCode、toString
  2. Jackson 序列化:处理 LocalDateTime 和集合类型
  3. 防御性编程:参数校验、异常处理
  4. 函数式风格:使用 Stream API 处理集合

案例 2:配置文件热加载(生产级实现)

需求:应用运行时动态加载配置文件变更,无需重启

java 复制代码
public class HotReloadConfig {
    private final Path configFile;
    private final Properties props = new Properties();
    private volatile long lastModified;
    private final ScheduledExecutorService scheduler;
    
    public HotReloadConfig(Path configFile) {
        this.configFile = configFile;
        this.scheduler = Executors.newScheduledThreadPool(1, 
            r -> {
                Thread t = new Thread(r, "config-reloader");
                t.setDaemon(true);
                return t;
            });
        loadConfig();
        startWatcher();
    }
    
    private void loadConfig() {
        try {
            if (Files.exists(configFile)) {
                long currentModified = Files.getLastModifiedTime(configFile).toMillis();
                if (currentModified != lastModified) {
                    synchronized (props) {
                        try (Reader reader = Files.newBufferedReader(configFile, 
                                StandardCharsets.UTF_8)) {
                            props.load(reader);
                            lastModified = currentModified;
                            System.out.println("配置已重新加载: " + configFile);
                        }
                    }
                }
            }
        } catch (IOException e) {
            System.err.println("加载配置失败: " + e.getMessage());
        }
    }
    
    private void startWatcher() {
        scheduler.scheduleWithFixedDelay(this::loadConfig, 5, 5, TimeUnit.SECONDS);
    }
    
    public String getProperty(String key, String defaultValue) {
        synchronized (props) {
            return props.getProperty(key, defaultValue);
        }
    }
    
    public void shutdown() {
        scheduler.shutdown();
    }
}

Maven 热启动部署插件实战

在开发过程中,频繁重启应用会浪费大量时间。Maven 提供了多个热启动插件,可以在代码变更后自动重新编译和部署。

1. Spring Boot DevTools(推荐)

Spring Boot DevTools 提供了自动重启、LiveReload 等功能。

xml 复制代码
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
</dependencies>

工作原理

  • 类加载器分离 :DevTools 使用两个类加载器
    • base classloader:加载不常变的类(第三方库)
    • restart classloader:加载应用代码
  • 文件监控 :监控 classpath 下的文件变更
  • 自动重启 :检测到变更后,只重启 restart classloader,速度更快

配置示例

yaml 复制代码
# application.yml
spring:
  devtools:
    restart:
      enabled: true
      # 排除不需要重启的资源
      exclude: static/**,public/**,templates/**
      # 监控额外路径
      additional-paths: src/main/java
    livereload:
      enabled: true  # 启用 LiveReload(浏览器自动刷新)

2. JRebel(商业插件,性能最佳)

JRebel 是 ZeroTurnaround 开发的商业热部署工具,无需重启即可加载类变更。

xml 复制代码
<!-- pom.xml -->
<plugin>
    <groupId>org.zeroturnaround</groupId>
    <artifactId>jrebel-maven-plugin</artifactId>
    <version>1.2.0</version>
    <executions>
        <execution>
            <id>generate-rebel-xml</id>
            <phase>process-resources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>

启动方式

bash 复制代码
# 使用 JRebel 启动
java -agentpath:/path/to/jrebel/lib/jrebel64.so -jar app.jar

3. DCEVM + HotswapAgent(开源替代方案)

DCEVM(Dynamic Code Evolution VM)是 JVM 的修改版本,支持更强大的热替换。

xml 复制代码
<!-- pom.xml -->
<plugin>
    <groupId>org.hotswap.agent</groupId>
    <artifactId>hotswap-agent-maven-plugin</artifactId>
    <version>1.4.0</version>
    <executions>
        <execution>
            <phase>compile</phase>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

4. Maven 插件:spring-boot-maven-plugin(开发模式)

xml 复制代码
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <fork>true</fork>
        <addResources>true</addResources>
        <!-- 开发模式:支持热部署 -->
        <jvmArguments>
            -XX:+UseG1GC
            -Dspring.devtools.restart.enabled=true
        </jvmArguments>
    </configuration>
</plugin>

启动命令

bash 复制代码
# 开发模式启动(支持热部署)
mvn spring-boot:run

# 或使用 IDE 的 Run Configuration

5. 综合对比

工具 类型 性能 成本 适用场景
Spring Boot DevTools 开源 中等(需重启) 免费 Spring Boot 项目
JRebel 商业 最佳(无需重启) 付费 大型项目、生产环境
DCEVM + HotswapAgent 开源 高(需重启 JVM) 免费 需要深度热替换
Maven 插件 开源 低(完整重启) 免费 简单项目

最佳实践

java 复制代码
// 1. 开发环境使用 DevTools
// application-dev.yml
spring:
  devtools:
    restart:
      enabled: true

// 2. 生产环境禁用
// application-prod.yml
spring:
  devtools:
    restart:
      enabled: false

// 3. 使用条件注解
@ConditionalOnProperty(
    name = "spring.devtools.restart.enabled",
    havingValue = "true",
    matchIfMissing = true
)
@Configuration
public class DevToolsConfig {
    // DevTools 配置
}

热部署限制

  • 不支持:修改方法签名、添加/删除字段、修改类继承关系
  • 支持:修改方法体、添加新方法、修改注解
  • 建议:重大变更时仍需要完整重启

应用场景

  • 微服务配置管理 :基于 Properties / YAML 加载环境配置,结合 Optional 处理缺省值。
  • CLI 工具/脚本替代:利用标准输入输出、文件 API 构建自动化脚本(如发布、巡检)。
  • 数据清洗/批处理 :使用 List + Stream 快速实现 CSV 解析、过滤、聚合。
  • 运行时参数校验Objects.requireNonNullPattern 正则,保障输入合法性。
  • 日志审计java.util.logging 或 SLF4J 快速接入,演示 LoggerFactory.getLogger


高频面试问答(深度解析)

1. JDK、JRE、JVM 的区别和关系?

标准答案

  • JVM (Java Virtual Machine):Java 虚拟机,负责执行字节码,管理内存(堆、栈、方法区),垃圾回收等
  • JRE (Java Runtime Environment):Java 运行时环境 = JVM + 核心类库(rt.jar 等)
  • JDK (Java Development Kit):Java 开发工具包 = JRE + 开发工具(javac、javap、jconsole 等)

深入追问与回答思路

Q: JVM 有哪些实现?

  • HotSpot:Oracle/OpenJDK 默认,使用 JIT 编译优化
  • GraalVM:高性能多语言运行时,支持 AOT 编译
  • Eclipse OpenJ9:IBM 贡献,内存占用更小
  • Zing:Azul 商业版,低延迟 GC

Q: 为什么需要 JRE?不能直接用 JVM 吗?

  • JVM 只负责执行字节码,但程序需要调用 java.lang.*java.util.* 等核心类库
  • JRE 提供了这些标准库的实现,如 StringArrayListHashMap

Q: 生产环境应该用 JDK 还是 JRE?

  • 推荐使用 JRE 或自定义运行时 (通过 jlink 创建)
  • 原因:体积更小、安全性更高(不包含开发工具)
  • 示例:jlink --module-path $JAVA_HOME/jmods --add-modules java.base --output minimal-jre

2. String 为什么设计成不可变的?

标准答案

  1. 安全性:作为参数传递时不会被修改(如 ClassLoader、网络连接)
  2. 线程安全:多线程环境下可安全共享,无需同步
  3. 缓存优化:字符串常量池(String Pool)可复用相同字符串,节省内存
  4. HashCode 缓存hash 字段缓存 hashCode,提升 HashMap 等集合性能

源码分析

java 复制代码
// JDK 8 及之前
public final class String {
    private final char[] value;  // final 确保引用不可变
    private int hash;  // 缓存 hashCode
    
    // 构造器创建新数组,不直接引用外部数组
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
}

// JDK 9+ 优化:使用 byte[] + coder
public final class String {
    private final byte[] value;  // Latin1 或 UTF-16
    private final byte coder;   // 0=Latin1, 1=UTF16
}

深入追问与回答思路

Q: String 不可变会带来性能问题吗?

  • :频繁字符串拼接会创建大量临时对象

  • 解决方案 :使用 StringBuilderStringBuffer

  • 性能对比

    java 复制代码
    // 错误:O(n²) 复杂度
    String s = "";
    for (int i = 0; i < 10000; i++) {
        s += i;  // 每次创建新对象
    }
    
    // 正确:O(n) 复杂度
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 10000; i++) {
        sb.append(i);
    }
    String s = sb.toString();

Q: String.intern() 的作用和风险?

  • 作用:将字符串放入常量池,相同内容的字符串返回同一引用
  • 风险
    • JDK 6 及之前:常量池在永久代,可能导致 OOM
    • JDK 7+:常量池在堆中,但仍需谨慎使用
  • 使用场景:大量重复字符串的场景(如日志解析)

Q: 如何实现可变字符串?

  • 使用 StringBuilder(非线程安全,性能更高)
  • 使用 StringBuffer(线程安全,性能较低)
  • 或使用 char[] 数组手动管理

3. Switch 表达式支持哪些类型?有什么新特性?

标准答案

  • JDK 7 之前 :只支持 byteshortintchar、枚举
  • JDK 7+ :支持 String(通过 hashCode()equals() 比较)
  • JDK 14+ :支持 switch 表达式,使用 -> 语法,支持 yield 返回值
  • JDK 17+:支持模式匹配(预览)
  • JDK 21+:模式匹配正式版

深入追问与回答思路

Q: Switch 为什么不能支持 long、float、double?

  • 技术原因:Switch 使用跳转表(Jump Table)实现,需要连续整数值
  • longfloatdouble 范围太大,无法构建跳转表
  • 替代方案 :使用 if-elseMap<Long, Runnable> 映射

Q: Switch 的跳转表(Jump Table)实现原理?

跳转表(Jump Table)机制

Switch 语句在编译时会根据 case 值的分布情况选择不同的实现策略:

  1. 跳转表(Table Switch):当 case 值连续或接近连续时使用
  2. 查找表(Lookup Switch):当 case 值稀疏时使用

Table Switch 实现原理

java 复制代码
// 源代码
int day = 3;
String dayName;
switch (day) {
    case 1: dayName = "Monday"; break;
    case 2: dayName = "Tuesday"; break;
    case 3: dayName = "Wednesday"; break;
    case 4: dayName = "Thursday"; break;
    case 5: dayName = "Friday"; break;
    default: dayName = "Unknown";
}

// 编译器生成的字节码(伪代码)
int min = 1, max = 5;
if (day < min || day > max) {
    goto default_label;
}
// 跳转表:直接通过索引跳转,O(1) 时间复杂度
jump_table[day - min] -> {
    0: "Monday",
    1: "Tuesday",
    2: "Wednesday",
    3: "Thursday",
    4: "Friday"
}

Lookup Switch 实现原理

java 复制代码
// 源代码(case 值稀疏)
int value = 100;
switch (value) {
    case 1: break;
    case 100: break;
    case 1000: break;
    default: break;
}

// 编译器生成的字节码(伪代码)
// 使用二分查找,O(log n) 时间复杂度
lookup_table = [
    {key: 1, offset: label1},
    {key: 100, offset: label100},
    {key: 1000, offset: label1000}
]
binary_search(lookup_table, value);

查看字节码验证

bash 复制代码
# 编译 Java 文件
javac SwitchExample.java

# 查看字节码
javap -c SwitchExample

# 输出示例
public static void testSwitch(int);
  Code:
     0: iload_0
     1: tableswitch {  // Table Switch
           1: 28
           2: 35
           3: 42
           default: 49
        }
    28: ldc #2  // String "Monday"
    30: astore_1
    31: goto 56
    ...

模式匹配中的跳转表使用

JDK 17+ 的模式匹配(Pattern Matching)在编译时也会使用跳转表优化。

java 复制代码
// 模式匹配示例
Object obj = "Hello";
String result = switch (obj) {
    case String s when s.length() > 5 -> "Long: " + s;
    case String s -> "Short: " + s;
    case Integer i -> "Number: " + i;
    case null -> "Null";
    default -> "Unknown";
};

// 编译器优化策略:
// 1. 类型判断:使用 instanceof 检查
// 2. 条件判断:when 子句编译为 if 条件
// 3. 跳转表:相同类型的 case 可能使用跳转表优化

模式匹配编译优化示例

java 复制代码
// 源代码
int value = 5;
String result = switch (value) {
    case 1, 2, 3 -> "Small";
    case 4, 5, 6 -> "Medium";
    case 7, 8, 9 -> "Large";
    default -> "Unknown";
};

// 编译器可能生成的优化代码(伪代码)
// 使用跳转表,O(1) 时间复杂度
if (value >= 1 && value <= 9) {
    result = jump_table[value - 1];  // 直接索引
} else {
    result = "Unknown";
}

性能对比

实现方式 时间复杂度 适用场景
Table Switch O(1) case 值连续或接近连续
Lookup Switch O(log n) case 值稀疏
if-else 链 O(n) 少量分支,编译器可能优化为跳转表
模式匹配 O(1) 或 O(log n) 根据模式复杂度决定

实际应用建议

  • 连续整数:使用 switch,编译器会优化为 Table Switch
  • 枚举类型:使用 switch,性能最优
  • 字符串 :JDK 7+ 支持,使用 hashCode() + equals() 比较
  • 模式匹配:JDK 17+,类型判断 + 条件判断一体化,性能优秀

Q: Switch 表达式的优势?

java 复制代码
// 传统 switch(容易忘记 break)
int result;
switch (day) {
    case 1:
        result = 1;
        break;  // 容易忘记
    case 2:
        result = 2;
        break;
    default:
        result = 0;
}

// Switch 表达式(更简洁、安全)
int result = switch (day) {
    case 1 -> 1;  // 自动 break
    case 2 -> 2;
    default -> 0;
};

Q: 模式匹配的实际应用?

java 复制代码
// 类型判断 + 类型转换 + 条件判断一体化
Object obj = getUserInput();
String result = switch (obj) {
    case String s when s.length() > 10 -> "长字符串: " + s;
    case String s -> "短字符串: " + s;
    case Integer i when i > 0 -> "正数: " + i;
    case Integer i -> "非正数: " + i;
    case null -> "空值";
    default -> "未知类型";
};

4. try-with-resources 的实现原理?

标准答案

  • 编译器自动生成 finally 块,调用 AutoCloseable.close()
  • 支持多个资源声明,按声明顺序逆序关闭
  • 如果关闭时抛出异常,会作为抑制异常(Suppressed Exception)附加到主异常

源码级分析

java 复制代码
// 源代码
try (FileInputStream fis = new FileInputStream("file.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    // 使用资源
}

// 编译器生成的等价代码(简化版)
FileInputStream fis = new FileInputStream("file.txt");
Throwable primaryException = null;
try {
    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    Throwable secondaryException = null;
    try {
        // 使用资源
    } catch (Throwable t) {
        secondaryException = t;
        throw t;
    } finally {
        if (br != null) {
            if (secondaryException != null) {
                try {
                    br.close();
                } catch (Throwable suppressed) {
                    secondaryException.addSuppressed(suppressed);
                }
            } else {
                br.close();
            }
        }
    }
} catch (Throwable t) {
    primaryException = t;
    throw t;
} finally {
    if (fis != null) {
        if (primaryException != null) {
            try {
                fis.close();
            } catch (Throwable suppressed) {
                primaryException.addSuppressed(suppressed);
            }
        } else {
            fis.close();
        }
    }
}

深入追问与回答思路

Q: 如何自定义资源类?

java 复制代码
public class DatabaseConnection implements AutoCloseable {
    private Connection conn;
    
    public DatabaseConnection(String url) throws SQLException {
        this.conn = DriverManager.getConnection(url);
    }
    
    @Override
    public void close() throws SQLException {
        if (conn != null && !conn.isClosed()) {
            conn.close();
            System.out.println("连接已关闭");
        }
    }
    
    public Connection getConnection() {
        return conn;
    }
}

// 使用
try (DatabaseConnection db = new DatabaseConnection("jdbc:mysql://...")) {
    // 使用数据库连接
}  // 自动关闭,无需手动调用 close()

Q: 如果 close() 抛出异常会怎样?

  • 如果主代码块正常执行,close() 的异常会正常抛出
  • 如果主代码块已抛出异常,close() 的异常会作为抑制异常附加
  • 可通过 Throwable.getSuppressed() 获取所有抑制异常

5. ArrayList 与 LinkedList 的差异?

标准答案

  • 底层结构ArrayList 基于动态数组,LinkedList 基于双向链表
  • 随机访问ArrayList O(1),LinkedList O(n)
  • 插入删除ArrayList 平均 O(n),LinkedList O(1)(已知位置)
  • 内存占用ArrayList 更紧凑,LinkedList 需要额外存储前后指针

源码分析

java 复制代码
// ArrayList 扩容机制
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);  // 1.5倍
    if (newCapacity < minCapacity) {
        newCapacity = minCapacity;
    }
    elementData = Arrays.copyOf(elementData, newCapacity);
}

// LinkedList 节点结构
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
}

深入追问与回答思路

Q: 什么时候用 ArrayList,什么时候用 LinkedList?

  • ArrayList 适用
    • 频繁随机访问(如 get(index)
    • 主要在尾部添加元素
    • 内存敏感场景
  • LinkedList 适用
    • 频繁在中间插入/删除
    • 需要实现队列/双端队列(Deque
    • 元素数量变化大,避免频繁扩容

Q: ArrayList 的扩容策略为什么是 1.5 倍?

  • 平衡内存和性能
    • 太小(如 1.1 倍):频繁扩容,性能差
    • 太大(如 2 倍):浪费内存
    • 1.5 倍是经验值,在大多数场景下表现良好

Q: LinkedList 真的比 ArrayList 快吗?

  • 不一定
    • 虽然插入删除是 O(1),但需要先定位到位置,定位是 O(n)
    • 实际测试中,ArrayList 由于缓存友好性,在小数据量时可能更快
    • 建议:根据实际场景做性能测试,不要盲目选择

6. 如何选择 JDK 版本?

标准答案

  • 生产环境首选 LTS 版本:JDK 8、11、17、21、25
  • 评估因素
    1. 依赖库兼容性(Spring、MyBatis 等)
    2. 性能收益(GC 改进、虚拟线程等)
    3. 新特性需求(Record、Pattern Matching 等)
    4. 团队技术栈和迁移成本

版本选择建议

版本 适用场景 迁移难度
JDK 8 稳定、生态成熟,适合保守项目
JDK 11 首个 LTS,云原生优化
JDK 17 推荐升级目标,特性丰富
JDK 21 LTS,虚拟线程等新特性 高(需评估兼容性)
JDK 25 最新 LTS,最新语言特性 高(需充分测试)

深入追问与回答思路

Q: 如何评估迁移风险?

  1. 使用 jdeps 分析依赖

    bash 复制代码
    jdeps --multi-release 17 --class-path . your-app.jar
  2. 使用 jdeprscan 检查已弃用 API

    bash 复制代码
    jdeprscan your-app.jar
  3. 在测试环境充分验证:功能测试、性能测试、压力测试

Q: 虚拟线程(JDK 21)适合哪些场景?

  • 适合:IO 密集型应用(网络请求、数据库查询)
  • 不适合:CPU 密集型任务、需要线程亲和的场景
  • 示例:微服务网关、API 聚合服务

Q: 如何平滑升级?

  1. 双版本并行:新功能用新版本,旧功能保持旧版本
  2. 灰度发布:先升级部分服务,观察稳定性
  3. 回滚方案:保留旧版本镜像,随时回滚
  4. 监控告警:关注 GC、线程、内存等指标

延伸阅读

  • 《Effective Java》第 3 版:条款 1-20 聚焦语言核心。
  • 官方文档:OpenJDK Language Guide、Java Tutorials。
  • 工具链:SDKMAN!、JEnv,便于多版本 JDK 切换。
  • 推荐实践:建立 GitHub Template Repo,包含基础代码、构建脚本、CI 工作流,便于快速起步。
相关推荐
superman超哥2 小时前
仓颉语言中网络套接字封装的深度剖析与工程实践
c语言·开发语言·c++·python·仓颉
白露与泡影2 小时前
2026年Java面试题目收集整理归纳(持续更新)
java·开发语言·面试
百***78752 小时前
【技术教程】3步极速接入GPT-5.1:零门槛体验多模态AI能力
android·java·人工智能·gpt·opencv
默 语2 小时前
IPIDEA 代理技术在海外品牌社媒数据采集中的实操落地(Instagram 营销分析案例版)
java·人工智能·ai·ai编程
墨痕诉清风2 小时前
java漏洞集合工具(Struts2、Fastjson、Weblogic(xml)、Shiro、Log4j、Jboss、SpringCloud)
xml·java·struts·安全·web安全·spring cloud·log4j
辞旧 lekkk2 小时前
【c++】c++11(上)
开发语言·c++·学习·萌新
程序员阿鹏2 小时前
SpringBoot自动装配原理
java·开发语言·spring boot·后端·spring·tomcat·maven
Andy工程师2 小时前
一个接口可以有多个实现类
java
彭世瑜2 小时前
C/C++:libfort用于在终端输出表格
c语言·开发语言·c++