> For the complete documentation index, see [llms.txt](https://lizh.gitbook.io/knowledge/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lizh.gitbook.io/knowledge/java-xi-lie/0102java-gao-ji-pian.md).

# 02 Java高级篇

Java 是一门面向对象编程语言，不仅吸收了 C++ 语言的各种优点，还摒弃了 C++ 里难以理解的多继承、指针等概念，因此，Java 语言具有功能强大和简单易用两个特征。Java 语言作为静态面向对象编程语言的代表，极好地实现了面向对象理论，允许程序员以优雅的思维方式进行复杂的编程。

Java 具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。

Java 可以编写桌面应用程序、Web 应用程序、分布式系统和嵌入式系统应用程序等。

## 线程与并发

并发编程是 Java 开发中非常重要的一环，它能帮助我们充分利用 CPU 资源，提升程序性能。

**核心概念：**

* **程序**：是一段静态的代码，存储在磁盘上，是指令的集合。
* **进程**：是程序的一次动态执行过程，每个进程都有独立的内存空间和系统资源。
* **线程**：是进程中的一个执行单元，是 CPU 调度和分配的基本单位。一个进程可以包含多个线程，它们共享进程的内存空间，这使得线程间的通信更加容易[-1](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html?is-external=true")。

**注意**：只有当进程中所有的非守护线程都执行完毕后，进程才会结束。

### 创建线程

* 通过实现 Runnable 接口；
* 通过继承 Thread 类本身；
* 通过 Callable 和 Future 创建线程。

#### 实现 Runnable 接口

这是最推荐的方式，因为它实现了任务与线程的解耦，并且更灵活。

**核心步骤**：

* 定义一个类实现 `Runnable` 接口。
* 实现 `run()` 方法，编写线程执行体。
* 创建 `Thread` 对象，将 `Runnable` 实例作为构造参数传入。
* 调用 `Thread` 对象的 `start()` 方法启动线程。

```java
public class Main {
    public static void main(String[] args) {
        MyRunnable task = new MyRunnable();
        Thread thread = new Thread(task, "我的工作线程");
        // 主线程调用 start() 后，不会等待新线程执行，而是继续运行自己的代码。
        // 新线程何时执行，取决于操作系统的调度，通常稍晚于主线程的后续语句。
        thread.start();
        System.out.println("主线程结束。");
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 正在运行。");
    }
}
```

**优点**：避免了单继承的限制；更适合处理多个线程共享同一资源的情况，能有效解耦。

#### 继承 Thread 类

这种方式编写更简单，可以直接使用 `this` 操作当前线程，但由于 Java 的单继承特性，灵活度较差。

**核心步骤**：

* 定义一个类继承 `Thread` 类。
* 重写 `run()` 方法。
* 创建该子类的实例。
* 调用实例的 `start()` 方法启动线程。

```java
public class Main {
    public static void main(String[] args) {
        MyThread t = new MyThread("我的线程");
        t.start();
        System.out.println("主线程结束。");
    }
}

class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.getName() + " 正在运行。");
    }
}
```

**优点**：编写简单，在 `run()` 方法内部可以直接使用 `this` 调用线程实例的方法。

#### 实现 Callable 接口

这种方式允许线程执行完毕后返回一个结果，并且可以抛出异常。

**核心步骤**：

* 定义一个类实现 `Callable` 接口，并指定泛型为返回值类型。
* 实现 `call()` 方法。
* 创建 `FutureTask` 对象来包装 `Callable` 实例。
* 将 `FutureTask` 对象作为目标传递给 `Thread` 并启动。
* 调用 `FutureTask` 的 `get()` 方法获取返回值（此方法会阻塞直到线程结束）。

```java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable callable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask, "计算线程");
        thread.start();

        System.out.println("主线程可以继续做其他事...");

        String result = futureTask.get();
        System.out.println("获取到结果: " + result);
    }
}

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("正在执行复杂计算...");
        Thread.sleep(1000);
        return "计算结果是 42";
    }
}

```

**优点**：能获取线程执行结果，并能处理异常。

### 线程的状态

Java 规范中只定义了线程的 4 种状态：新建状态、可运行状态、阻塞状态和死亡状态。

其中可运行状态可以细分为就绪状态和运行状态。

* **新建状态**：`new Thread(...)` 后，线程对象创建，但还未启动。
* **就绪状态**：调用 `start()` 方法后，线程处于就绪状态，等待 CPU 时间片。
* **运行状态**：就绪状态的线程获得 CPU 执行周期，就处于运行状态。线程一旦运行，只是在 CPU 周期内获得执行权利，而一时 CPU 的时间片用完，操作系统的调度程序重新选择执行的线程。
* **阻塞状态**：线程暂时放弃 CPU，暂停运行。例如，调用了 `sleep()`, `wait()` 或进行 I/O 操作时。
* **死亡状态**：线程的 `run()` 方法执行完毕，或因为未捕获的异常而意外终止。

### 线程同步

当多个线程同时访问共享资源时，为了保证数据的一致性，需要引入线程同步机制。

**线程同步机制：**

* **synchronized** 关键字
* **ReentrantLock**
* **volatile** 关键字

#### synchronized 关键字（最基础）

`synchronized` 可以修饰方法或代码块，保证同一时刻只有一个线程能执行该段代码。

```java
public class SafeCounter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
    public synchronized int getCount() {
        return count;
    }
}
```

#### ReentrantLock

`ReentrantLock` 是 `Lock` 接口的实现，它提供了比 `synchronized` 更灵活的锁机制，如尝试非阻塞地获取锁 (`tryLock()`)、可中断地获取锁等。

```java
import java.util.concurrent.locks.ReentrantLock;

public class SafeCounter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}
```

#### volatile 关键字

`volatile` 保证变量的**可见性**：一个线程修改了 volatile 变量，其他线程能立即看到最新值。但它**不保证复合操作的原子性**。

**注意**：适用布尔标志位、状态切换等简单赋值操作，不适用于 `count++` 这类读-改-写操作。

```java
public class VolatileFlag {
    private volatile boolean running = true;

    public void stop() { running = false; }

    public void work() {
        while (running) {
            // 做事情，running 变化时能立即退出循环
        }
    }
}
```

### 线程的优先级

在 Java 中，每个线程都有一个优先级，范围是 **`Thread.MIN_PRIORITY`（1）** 到 **`Thread.MAX_PRIORITY`（10）**，默认优先级是 **`Thread.NORM_PRIORITY`（5）**。

**较高优先级的线程更有可能获得 CPU 时间片**，但：

* 优先级**不能保证**执行顺序，它只是一个**建议**，具体行为依赖于底层操作系统的实现。
* 某些操作系统（如 Linux）可能忽略优先级设置，将所有线程视为相同优先级。
* **不要依赖优先级来控制程序逻辑**，应使用同步机制（如锁、等待/通知等）。

```java
public class Main {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable(), "低优先级线程");
        Thread thread2 = new Thread(new MyRunnable(), "高优先级线程");

        thread1.setPriority(Thread.MIN_PRIORITY);
        thread2.setPriority(Thread.MAX_PRIORITY);

        thread1.start();
        thread2.start();
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            Math.random();
        }
        long endTime = System.currentTimeMillis();
        System.out.println(threadName + " 执行完毕，耗时：" + (endTime - startTime) + " ms");
    }
}
```

**注意事项：**

* **不要使用优先级解决竞争条件**：它不可靠，正确的做法是使用 `synchronized`、`Lock` 等同步工具。
* **优先级继承**：如果一个高优先级线程因等待锁而被阻塞，JVM 可能会临时提高持有锁的线程的优先级，避免优先级反转问题。
* **平台差异**：Windows 支持 10 个优先级，但某些 Unix 系统只支持 3 个优先级（1、5、10 会被映射到少数几个级别）。

### 多线程的使用

多线程的核心价值在于 **并发执行**，即让多个任务看起来是同时进行的。在实际开发中，合理使用多线程可以显著提升程序的响应速度和资源利用率。

**请注意**：如果创建太多的线程，程序执行的效率实际上是降低了，而不是提升了。因为，上下文的切换开销也很重要，如果创建了太多的线程，CPU 花费在上下文的切换的时间将多于执行程序的时间！

\*\*注意：\*\*多线程能大幅提升程序性能，但也会引入复杂性（死锁、竞态条件、内存可见性等）。

**判断线程是否结束：**

* **`Thread.join()`**：让当前线程等待指定线程死亡。
* **`Thread.isAlive()`**：检查线程是否处于活动状态。
* **`CountDownLatch`**：等待多个线程完成任务。
* **`ExecutorService + Future`**：提交任务后通过 `Future.get()` 阻塞等待结果。
* **`ExecutorService.awaitTermination()`**：等待线程池中所有任务完成。

```java
import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        // 创建 CountDownLatch，计数器为 2（需要等待两个线程完成）
        CountDownLatch latch = new CountDownLatch(2);

        // Lambda 表达式写法
        Runnable downloadTask = () -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("[下载] 第 " + i + " 块数据...");
                    Thread.sleep(300);
                }
                System.out.println("下载完成！");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown(); // 无论是否异常，都通知计数器减1
            }
        };

        // Lambda 表达式写法
        Runnable processTask = () -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("[处理] 第 " + i + " 块数据...");
                    Thread.sleep(200);
                }
                System.out.println("处理完成！");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown(); // 通知计数器减1
            }
        };

        new Thread(downloadTask, "DownloadThread").start();
        new Thread(processTask, "ProcessThread").start();

        // 主线程等待，直到计数器变为 0
        latch.await();

        System.out.println("所有线程都已结束");
    }
}
```

如果多个线程需要访问同一个变量（如计数器、缓存），必须使用**同步机制**（`synchronized`、`Lock`、`volatile` 等）保证数据一致性。

## 发送邮件（QQ）

使用 Java 发送邮件前，需要开启邮箱账号的 SMTP 服务，并获取一个**授权码**。这个授权码将用于代码中的身份验证，而不是邮箱密码。

获取发送邮件授权码（QQ）流程：`登录 QQ 邮箱 -> 设置 -> 账号与安全 -> 安全设置 -> POP3/IMAP/SMTP/Exchange/CardDAV 服务（已开启） -> 生成授权码`。

### 依赖 (Maven)

```java
<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.6.2</version>
</dependency>
```

### 示例代码

```java
import java.util.Properties;
import javax.mail.Session;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Message;
import javax.mail.Transport;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.InternetAddress;
public class QQMailMain {
    public static void main (String [] args) {
        String from = "544218211@qq.com";
        String authorizationCode = "xxx"; // 授权码
        String to = "1927344728@qq.com";

        Properties properties = System.getProperties();
        properties.put("mail.transport.protocol", "smtp");
        properties.put("mail.smtp.auth", "true");
        properties.put("mail.smtp.host", "smtp.qq.com");
        properties.put("mail.smtp.port", "465");
        properties.put("mail.smtp.ssl.enable", "true");
        properties.put("mail.smtp.ssl.protocols", "TLSv1.2");

        Session session = Session.getDefaultInstance(properties, new Authenticator(){
            public PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(from, authorizationCode);
            }
        });
        session.setDebug(true);
        try{
            MimeMessage message = new MimeMessage(session);
            message.setFrom(new InternetAddress(from));
            message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
            message.setSubject("Java 发送邮件!");
            message.setText("这是 Java 邮件的信息内容！005");
            Transport.send(message);
            System.out.println("邮件发送成功");
        }catch (MessagingException mex) {
            mex.printStackTrace();
        }
    }
}

```

**注意**：protocol、host、port 可以在`设置 -> 账号与安全 -> 安全设置 -> POP3/IMAP/SMTP/Exchange/CardDAV 服务（已开启） -> 查看配置方法` 的页面中查看。

## Swing GUI 编程

Swing 是 Java 官方提供的**图形用户界面（GUI）工具包**，用于开发跨平台的桌面应用程序。它从 JDK 1.2 开始被包含在 Java 标准库中，是 Java 桌面开发的主流技术之一。

Swing 是一套**轻量级**的组件库。所谓“轻量级”，是指 Swing 组件完全由 **Java 代码**绘制而成，不依赖操作系统的本地界面（native peers）。因此，Swing 应用程序在不同操作系统上（Windows、macOS、Linux）看起来**外观基本一致**，真正实现了“一次编写，到处运行”。

**Swing 与 AWT：**

* **AWT**：早期的 GUI 工具包，依赖于本地操作系统的图形界面，因此**外观和功能在不同平台上差异较大**。
* **Swing**：在 AWT 基础上发展而来，使用纯 Java 代码**模拟**各种控件，因此是**跨平台**的，外观可以保持一致。

**注意**：Swing 并不是完全取代 AWT，它仍然依赖 AWT 的事件模型和布局管理器（如 `BorderLayout`、`FlowLayout` 等）。

### Swing 类库结构

Swing 类库设计非常精巧，其核心设计思想是 **MVC（Model-View-Controller）** 模式，并且所有可视化组件都继承自 `JComponent`。

**MVC 设计模式：**

* **Model（模型）**：存储数据和业务逻辑，不关心如何显示。
* **View（视图）**：负责将模型中的数据以某种形式显示给用户。
* **Controller（控制器）**：接收用户输入（鼠标、键盘事件），并更新模型或视图。

Swing 并没有严格分离 View 和 Controller，而是将两者合并为 **UI 委托**，即每个组件都有一个对应的 `ComponentUI` 对象，该对象同时负责**绘制（View）和事件处理（Controller）**。

**Swing 核心组件：**

* **顶层容器**：`JFrame`（主窗口）、`JDialog`（对话框）、`JApplet`（嵌入网页的 Applet，现已过时）。一个 GUI 程序必须至少有一个顶层容器。
* **中间容器**：`JPanel`、`JScrollPane`、`JTabbedPane`、`JToolBar` 等。它们不能独立显示，必须添加到顶层容器或其他容器中。
* **基本控件**：`JButton`、`JLabel`、`JTextField`、`JCheckBox`、`JRadioButton`、`JComboBox` 等。

`JComponent` 是所有 Swing 轻量级组件的**抽象基类**，它继承自 AWT 的 `Container` 类。

```shell
java.lang.Object
  └─ java.awt.Component
       └─ java.awt.Container
            └─ javax.swing.JComponent (Swing 组件的基类)
                 ├─ JButton
                 ├─ JLabel
                 ├─ JTextField
                 ├─ JPanel
                 ├─ JScrollPane
                 ├─ JTable
                 ├─ JTree
                 └─ ...
            └─ java.awt.Window
                 └─ java.awt.Frame
                      └─ javax.swing.JFrame (顶层窗口)
                 └─ java.awt.Dialog
                      └─ javax.swing.JDialog (对话框)
```

### 布局管理器

在 Swing 图形界面编程中，**布局管理器**负责决定容器内组件的**位置和大小**。使用布局管理器可以让我们不必为每个组件硬编码坐标，避免了组件重叠、窗口缩放时布局错乱等问题。每个容器都有一个默认的布局管理器。

#### 边框布局（BorderLayout）

将容器分为五个区域：`NORTH`（上）、`SOUTH`（下）、`EAST`（右）、`WEST`（左）、`CENTER`（中）。每个区域**最多只能放置一个组件**；如果某个区域没有组件，`CENTER` 会扩张占用其空间。如果同一区域添加多个组件，只有最后添加的那个会显示。

**默认用于**：`JFrame`、`JDialog`、`JWindow`（顶层容器）。

```java
import javax.swing.JFrame;
import javax.swing.JButton;
import java.awt.BorderLayout;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("BorderLayout 示例");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);
        frame.setLayout(new BorderLayout());

        frame.add(new JButton("北"), BorderLayout.NORTH);
        frame.add(new JButton("南"), BorderLayout.SOUTH);
        frame.add(new JButton("东"), BorderLayout.EAST);
        frame.add(new JButton("西"), BorderLayout.WEST);
        frame.add(new JButton("中"), BorderLayout.CENTER);

        frame.setVisible(true);
    }
}
```

#### 流式布局（FlowLayout）

组件按照添加顺序**从左到右**排列，一行排满后自动换到下一行。每个组件保持其**首选大小**（不拉伸）。可以通过构造函数指定对齐方式（左对齐、居中对齐、右对齐）以及水平和垂直间距。

**默认用于**：`JPanel`、`JApplet`。

```java
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.FlowLayout;
public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("FlowLayout 示例");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(500, 100);

        JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
        for (int i = 1; i <= 8; i++) {
            panel.add(new JButton("按钮" + i));
        }
        frame.add(panel);
        frame.setVisible(true);
    }
}
```

#### 网格布局（GridLayout）

将容器划分为指定行数和列数的**等大网格**。每个组件都会填满整个单元格，尺寸完全由网格大小决定，**忽略组件的首选大小**。组件按照从左到右、从上到下的顺序依次放入。

```java
import javax.swing.JFrame;
import javax.swing.JButton;
import java.awt.GridLayout;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("GridLayout 示例");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 300);
        frame.setLayout(new GridLayout(4, 4, 5, 5));

        String[] labels = {
            "7", "8", "9", "/",
            "4", "5", "6", "*",
            "1", "2", "3", "-",
            "0", ".", "=", "+"
        };
        for (String label : labels) {
            frame.add(new JButton(label));
        }
        frame.setVisible(true);
    }
}
```

#### 卡片布局（CardLayout）

同一容器中可以放置多个组件（通常是面板），但**一次只显示其中一张“卡片”**。可以通过 `first()`, `last()`, `next()`, `previous()`, `show()` 等方法切换显示的卡片。常用于实现向导对话框、选项卡面板（JTabbedPane 内部也使用了类似概念）。

```java
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.CardLayout;
import java.awt.BorderLayout;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("CardLayout 示例");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 200);

        CardLayout cardLayout = new CardLayout();
        JPanel cardsPanel = new JPanel(cardLayout);

        JPanel card1 = new JPanel();
        card1.add(new JLabel("这是第一张卡片"));
        card1.add(new JButton("按钮A"));

        JPanel card2 = new JPanel();
        card2.add(new JLabel("这是第二张卡片"));
        card2.add(new JButton("按钮B"));

        cardsPanel.add(card1, "card1");
        cardsPanel.add(card2, "card2");

        JPanel controlPanel = new JPanel();
        JButton prevButton = new JButton("上一张");
        JButton nextButton = new JButton("下一张");
        controlPanel.add(prevButton);
        controlPanel.add(nextButton);

        prevButton.addActionListener(e -> cardLayout.previous(cardsPanel));
        nextButton.addActionListener(e -> cardLayout.next(cardsPanel));

        frame.add(cardsPanel, BorderLayout.CENTER);
        frame.add(controlPanel, BorderLayout.SOUTH);
        frame.setVisible(true);
    }
}
```

#### 网格包布局（GridBagLayout）

**最灵活、最复杂**。它基于网格，但允许组件**跨多行多列**，且每个组件可以有不同的对齐方式、填充方式和大小。使用 `GridBagConstraints` 对象为每个组件设置约束。

**常用约束字段：**

* `gridx`, `gridy`：组件左上角所在的单元格位置。
* `gridwidth`, `gridheight`：组件占用的列数和行数。
* `weightx`, `weighty`：分配额外空间的比例（当窗口缩放时，哪部分区域获得多余空间）。
* `fill`：当组件小于其显示区域时，是否拉伸填充（`NONE`, `HORIZONTAL`, `VERTICAL`, `BOTH`）。
* `anchor`：组件在其显示区域内的对齐方式（如 `CENTER`, `NORTHWEST` 等）。
* `insets`：组件的外边距。

```java
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JTextField;
import javax.swing.JPasswordField;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("登录");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(5, 5, 5, 5); // 外边距

        // 用户名标签
        gbc.gridx = 0; gbc.gridy = 0;
        gbc.anchor = GridBagConstraints.EAST;
        frame.add(new JLabel("用户名："), gbc);
        // 用户名输入框
        gbc.gridx = 1; gbc.gridy = 0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1.0;
        JTextField userField = new JTextField(15);
        frame.add(userField, gbc);

        // 密码标签
        gbc.gridx = 0; gbc.gridy = 1;
        gbc.fill = GridBagConstraints.NONE;
        gbc.weightx = 0;
        frame.add(new JLabel("密码："), gbc);
        // 密码输入框
        gbc.gridx = 1; gbc.gridy = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1.0;
        JPasswordField passField = new JPasswordField(15);
        frame.add(passField, gbc);

        // 按钮面板（两个按钮放在同一行）
        gbc.gridx = 0; gbc.gridy = 2;
        gbc.gridwidth = 2;          // 跨两列
        gbc.fill = GridBagConstraints.NONE;
        gbc.weightx = 0;
        gbc.anchor = GridBagConstraints.CENTER;
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(new JButton("登录"));
        buttonPanel.add(new JButton("取消"));
        frame.add(buttonPanel, gbc);

        frame.pack();
        frame.setVisible(true);
    }
}
```

#### 盒布局（BoxLayout）

将组件沿水平轴（X轴）或垂直轴（Y轴）顺序排列。与 `FlowLayout` 类似，但更强大：可以控制每个组件的对齐方式。通常与 `Box` 容器（默认使用 `BoxLayout`）一起使用。

```java
import javax.swing.JFrame;
import javax.swing.Box;
import javax.swing.JButton;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("BoxLayout 示例");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 200);

        Box verticalBox = Box.createVerticalBox(); // 垂直 Box
        verticalBox.add(Box.createVerticalGlue()); // 顶部弹性空间
        verticalBox.add(new JButton("按钮1"));
        verticalBox.add(Box.createVerticalStrut(10)); // 固定间距10像素
        verticalBox.add(new JButton("按钮2"));
        verticalBox.add(Box.createVerticalGlue()); // 底部弹性空间

        frame.add(verticalBox);
        frame.setVisible(true);
    }
}
```

### 事件处理

在 Swing 图形界面程序中，**事件处理**是用户与程序交互的核心。当用户点击按钮、输入文本、移动鼠标或关闭窗口时，就会产生相应的事件。

Swing 采用**事件委托模型**（也称为**监听器模型**）：事件源（如按钮）产生事件对象，然后将该事件对象发送给已注册的事件监听器，监听器中的回调方法被触发，从而执行相应的业务逻辑。

**常用的事件监听器接口：**

* `ActionListener`：处理按钮点击、菜单选择等动作。
* `MouseListener` / `MouseMotionListener`：处理鼠标事件。
* `KeyListener`：处理键盘事件。
* `FocusListener`：处理焦点得失事件。

```java
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("事件处理示例");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(400, 200);
            frame.setLocationRelativeTo(null);

            JPanel panel = new JPanel();

            JLabel label = new JLabel("请点击按钮");
            JButton button = new JButton("点我");

            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    label.setText("按钮被点击了！");
                }
            });

            // Lambda 表达式简化
            // button.addActionListener(e -> label.setText("按钮被点击了！"));

            panel.add(label);
            panel.add(button);
            frame.add(panel);
            frame.setVisible(true);
        });
    }
}
```

### 示例代码

```java
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.ButtonGroup;
import javax.swing.JRadioButton;
import javax.swing.JTextArea;
import javax.swing.JOptionPane;
import java.awt.Toolkit;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;

public class Main {
    static final int FRAME_WIDTH = 600;
    static final int FRAME_HEIGHT = 400;
    public static void main (String [] args) {
        JFrame frame = new JFrame();
        frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("Lizhao Swing");

        Toolkit kit = Toolkit.getDefaultToolkit();
        Dimension screenSize = kit.getScreenSize();
        int width = screenSize.width;
        int height = screenSize.height;
        frame.setLocation((width - FRAME_WIDTH) / 2, (height - FRAME_HEIGHT) / 2);

        JPanel panel1 = new JPanel();
        JButton button1 = new JButton("确定");
        JButton button2 = new JButton("取消");
        panel1.add(button1);
        panel1.add(button2);
        frame.add(panel1, BorderLayout.SOUTH);

        JPanel panel2 = new JPanel();
        ButtonGroup buttonGroup = new ButtonGroup();
        JRadioButton radio1 = new JRadioButton("Li");
        JRadioButton radio2 = new JRadioButton("Zhao");
        JRadioButton radio3 = new JRadioButton("李兆");
        buttonGroup.add(radio1);
        buttonGroup.add(radio2);
        buttonGroup.add(radio3);
        panel2.add(radio1);
        panel2.add(radio2);
        panel2.add(radio3);
        frame.add(panel2, BorderLayout.EAST);

        JPanel panel3 = new JPanel(new BorderLayout());
        String placeholder = "请输入内容";
        JTextArea textArea = new JTextArea(placeholder, 10, 30);
        textArea.setLineWrap(true);
        textArea.setToolTipText(placeholder);
        textArea.setAlignmentX(0);
        textArea.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                textArea.setText("");
            }

            @Override
            public void focusLost(FocusEvent e) {
                if (textArea.getText().isEmpty()) {
                    textArea.setText(placeholder);
                }
            }
        });
        panel3.add(textArea, BorderLayout.WEST);
        frame.add(panel3);

        button1.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String textAreaStr = textArea.getText();
                if (textAreaStr.contentEquals(placeholder)) {
                    int dialogValue = JOptionPane.showConfirmDialog(frame,"确定要关闭吗？","文本域没有输入内容", JOptionPane.YES_NO_OPTION);
                    System.out.println(dialogValue);
                    if (dialogValue == 0) {
                        frame.setVisible(false);
                        System.exit(0);
                    }
                    return;
                }
                System.out.println(textAreaStr);
                frame.setVisible(false);
                System.exit(0);
            }
        });
        button2.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                frame.setVisible(false);
                System.exit(0);
            }
        });

        frame.setVisible(true);
    }
}
```

## 网络编程

Java 网络编程是指在 Java 语言中使用 `java.net` 包提供的类和接口，实现不同计算机之间的数据交换。Java 支持两种主要的网络协议：**TCP**（传输控制协议）和 **UDP**（用户数据报协议）。

**网络编程基础概念：**

* **IP 地址**：网络中计算机的唯一标识（如 192.168.1.1）。
* **端口号**：0~~65535，标识计算机上的特定进程。1~~1023 为系统保留，常用端口如 HTTP（80）、HTTPS（443）。
* **Socket（套接字）**：网络通信的端点，包含 IP 地址 + 端口号。应用程序通过 Socket 发送和接收数据。

### Socket（套接字）

**Socket（套接字）** 是计算机网络编程中的一个抽象概念，它**封装了 IP 地址和端口号**，为应用程序提供了一种发送和接收数据的接口。可以把它理解成**网络通信的端点**（Endpoint）。

Socket 是一个**编程接口**（API），而 **TCP 和 UDP 是传输层协议**。Socket 为应用程序提供了使用 TCP 或 UDP 协议进行网络通信的手段。

### TCP 编程

**TCP（Transmission Control Protocol，传输控制协议）** 是一种面向连接的、可靠的、基于字节流的协议。它通过“三次握手”建立连接，保证数据包按顺序、无差错到达，适用于需要可靠传输的场景（如文件传输、网页浏览、邮件等）。

**TCP 客户端-服务器通信模型：**

* **服务器端**：使用 `ServerSocket` 监听指定端口，调用 `accept()` 等待客户端连接。连接成功后返回一个 `Socket` 对象，用于与客户端通信。
* **客户端**：使用 `Socket` 主动连接服务器的 IP 和端口。连接成功后即可通过输入/输出流进行数据交换。

**详细步骤：**

| 步骤         | 服务端                                         | 客户端                                          |
| ---------- | ------------------------------------------- | -------------------------------------------- |
| **启动**     | 创建 `ServerSocket` 并绑定端口 `8888`，开始监听         | 创建 `Socket` 连接服务器的 IP `127.0.0.1` 和端口 `8888` |
| **建立连接**   | 调用 `accept()` 阻塞，等待客户端连接                    | 连接成功后，`Socket` 构造函数返回                        |
| **获取流**    | 获取 `Socket` 的输入流和输出流                        | 获取 `Socket` 的输入流和输出流                         |
| **启动接收线程** | 创建一个线程，循环读取客户端发来的消息并打印                      | 创建一个线程，循环读取服务端发来的消息并打印                       |
| **主线程发送**  | 主线程从键盘读入消息，通过输出流发送给客户端                      | 主线程从键盘读入消息，通过输出流发送给服务端                       |
| **退出**     | 任何一方输入 `bye`，给对方发送 `bye` 后，双方均关闭 socket 并退出 | 同样                                           |

```java
import java.net.ServerSocket;
import java.net.Socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.IOException;

public class TCPServerMain {
    public static void main(String[] args) {
        final int PORT = 8888;
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("服务器已启动，等待客户端连接...");
            Socket socket = serverSocket.accept();
            System.out.println("客户端已连接：" + socket.getInetAddress().getHostAddress());

            // 获取输入输出流
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            // 启动线程：读取客户端发来的消息
            Thread readThread = new Thread(() -> {
                try {
                    String msg;
                    while ((msg = in.readLine()) != null) {
                        if (msg.equalsIgnoreCase("bye")) {
                            System.out.println("客户端请求结束对话");
                            out.println("bye");    // 通知客户端也退出
                            break;
                        }
                        System.out.println("客户端说：" + msg);
                    }
                } catch (IOException e) {
                    System.out.println("连接已断开");
                } finally {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    System.exit(0);
                }
            });
            readThread.start();

            // 主线程：从键盘读取消息并发送给客户端
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
            String input;
            while ((input = consoleReader.readLine()) != null) {
                out.println(input);
                if (input.equalsIgnoreCase("bye")) {
                    System.out.println("服务器结束对话");
                    break;
                }
            }
            socket.close();
            System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

```java
import java.net.Socket;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;

public class TCPClientMain {
    public static void main(String[] args) {
        final String SERVER_IP = "127.0.0.1";
        final int PORT = 8888;
        try (Socket socket = new Socket(SERVER_IP, PORT)) {
            System.out.println("已连接到服务器：" + SERVER_IP);

            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            // 启动线程：读取服务器发来的消息
            Thread readThread = new Thread(() -> {
                try {
                    String msg;
                    while ((msg = in.readLine()) != null) {
                        if (msg.equalsIgnoreCase("bye")) {
                            System.out.println("服务器请求结束对话");
                            out.println("bye");
                            break;
                        }
                        System.out.println("服务器说：" + msg);
                    }
                } catch (IOException e) {
                    System.out.println("连接已断开");
                } finally {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    System.exit(0);
                }
            });
            readThread.start();

            // 主线程：从键盘读取消息并发送给服务器
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
            String input;
            while ((input = consoleReader.readLine()) != null) {
                out.println(input);
                if (input.equalsIgnoreCase("bye")) {
                    System.out.println("客户端结束对话");
                    break;
                }
            }
            socket.close();
            System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

### UDP 编程

**UDP（User Datagram Protocol，用户数据报协议）** 是一种无连接的、不可靠的、基于数据报的协议。它不建立连接，直接将数据包发送给目标，效率高但可能丢包或乱序，适用于实时性要求高、可容忍少量丢失的场景（如视频流、DNS 查询、游戏等）。

**UDP 通信模型：**

* **发送端**：创建 `DatagramSocket`，将数据封装到 `DatagramPacket`（包含目标 IP 和端口），然后发送。
* **接收端**：创建 `DatagramSocket` 并绑定端口，准备一个空 `DatagramPacket` 接收数据，调用 `receive()` 阻塞等待。

UDP 是无连接的，通信双方互相知道对方的 IP 和端口即可直接发送数据报。

**详细步骤：**

| 步骤         | 服务端                                                 | 客户端                                                        |
| ---------- | --------------------------------------------------- | ---------------------------------------------------------- |
| **启动**     | 创建 `DatagramSocket` 并绑定端口 `9999`                    | 创建 `DatagramSocket` 并绑定端口 `9998`                           |
| **获取对方地址** | 调用 `receive()` 等待第一个数据包，从中获取客户端的 IP 和端口             | 主动向服务端发送第一条数据（内容无所谓，用于告知自己地址）                              |
| **启动接收线程** | 创建线程，循环接收数据报，提取消息打印                                 | 创建线程，循环接收数据报，提取消息打印                                        |
| **主线程发送**  | 主线程从键盘读入消息，封装成 `DatagramPacket`（目标地址为客户端 IP 和端口）并发送 | 主线程从键盘读入消息，封装成 `DatagramPacket`（目标地址为服务端 IP 和端口 `9999`）并发送 |
| **退出**     | 输入 `bye`，发送给对方 `bye` 后退出                            | 同样                                                         |

```java
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class UDPServerMain {
    public static void main(String[] args) throws Exception {
        final int SERVER_PORT = 9999;
        DatagramSocket socket = new DatagramSocket(SERVER_PORT);
        System.out.println("UDP 服务端启动，监听端口 " + SERVER_PORT);

        // 等待第一个包，获取客户端地址和端口
        byte[] buffer = new byte[1024];
        DatagramPacket firstPacket = new DatagramPacket(buffer, buffer.length);
        socket.receive(firstPacket);
        String initMsg = new String(firstPacket.getData(), 0, firstPacket.getLength());
        InetAddress clientAddr = firstPacket.getAddress();
        int clientPort = firstPacket.getPort();
        System.out.println("客户端已连接：" + clientAddr.getHostAddress() + ":" + clientPort);
        System.out.println("客户端说：" + initMsg);

        // 启动接收线程：持续接收客户端发来的消息
        Thread receiveThread = new Thread(() -> {
            try {
                while (true) {
                    byte[] recvBuf = new byte[1024];
                    DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);
                    socket.receive(recvPacket);
                    String msg = new String(recvPacket.getData(), 0, recvPacket.getLength());
                    if ("bye".equalsIgnoreCase(msg.trim())) {
                        System.out.println("客户端请求结束对话");
                        break;
                    }
                    System.out.println("客户端说：" + msg);
                }
            } catch (Exception e) {
                System.out.println("接收线程结束");
            } finally {
                socket.close();
                System.exit(0);
            }
        });
        receiveThread.start();

        // 主线程：从键盘读取消息并发送给客户端
        BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
        String input;
        while ((input = consoleReader.readLine()) != null) {
            byte[] sendData = input.getBytes();
            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, clientAddr, clientPort);
            socket.send(sendPacket);
            if ("bye".equalsIgnoreCase(input.trim())) {
                System.out.println("服务端结束对话");
                socket.close();
                System.exit(0);
            }
        }
    }
}
```

```java
import java.net.InetAddress;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class UDPClientMain {
    public static void main(String[] args) throws Exception {
        final int CLIENT_PORT = 9998;
        final String SERVER_IP = "127.0.0.1";
        final int SERVER_PORT = 9999;

        DatagramSocket socket = new DatagramSocket(CLIENT_PORT);
        InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
        System.out.println("UDP 客户端启动，本地端口 " + CLIENT_PORT);

        // 先发送一条初始化消息（可选）
        String initMsg = "Hello, 我是客户端";
        byte[] initData = initMsg.getBytes();
        DatagramPacket initPacket = new DatagramPacket(initData, initData.length, serverAddr, SERVER_PORT);
        socket.send(initPacket);

        // 启动接收线程：持续接收服务端发来的消息
        Thread receiveThread = new Thread(() -> {
            try {
                while (true) {
                    byte[] recvBuf = new byte[1024];
                    DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);
                    socket.receive(recvPacket);
                    String msg = new String(recvPacket.getData(), 0, recvPacket.getLength());
                    if ("bye".equalsIgnoreCase(msg.trim())) {
                        System.out.println("服务端请求结束对话");
                        break;
                    }
                    System.out.println("服务端说：" + msg);
                }
            } catch (Exception e) {
                System.out.println("接收线程结束");
            } finally {
                socket.close();
                System.exit(0);
            }
        });
        receiveThread.start();

        // 主线程：从键盘读取消息并发送给服务端
        BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
        String input;
        while ((input = consoleReader.readLine()) != null) {
            byte[] sendData = input.getBytes();
            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddr, SERVER_PORT);
            socket.send(sendPacket);
            if ("bye".equalsIgnoreCase(input.trim())) {
                System.out.println("客户端结束对话");
                socket.close();
                System.exit(0);
            }
        }
    }
}
```

### URL处理

URL（Uniform Resource Locator），统一资源定位符，有时也被俗称为网页地址。表示为互联网上的资源，如：http 或者 FTP 地址。

`java.net.URL` 类用于表示统一资源定位符，可以打开网络连接并读取资源（如网页、文件等）。

```java
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.net.URL;
import java.net.URLConnection;

public class Main {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://www.izhao.com.cn");
        URLConnection conn = url.openConnection();
        try (InputStream is = conn.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}
```

### http 接口

```java
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class Main {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);

        server.createContext("/hello", new HelloHandler());

        server.start();
        System.out.println("服务器已启动，访问 http://localhost:8080/hello");
    }

    static class HelloHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange exchange) throws IOException {
            String response = "Hello, World! 这是一个简单的 API。";
            // 设置响应头
            exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8");
            // 发送响应头（200 表示成功，response.length() 是响应体长度）
            exchange.sendResponseHeaders(200, response.getBytes().length);
            // 获取输出流，写入响应体
            OutputStream os = exchange.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }
}
```

## JDBC

JDBC（Java Database Connectivity）是 Java 程序访问数据库的 API，是一组标准的 Java 语言中的接口和类，它为多种关系型数据库提供了统一的访问方式。

**核心步骤：**

* **建立连接**：使用 `DriverManager.getConnection()` 获取 `Connection` 对象。
* **创建 Statement**：通过 `Connection` 创建 `Statement` 或 `PreparedStatement` 对象。
* **执行 SQL**：执行 `executeQuery()`（查询）或 `executeUpdate()`（增删改）方法。
* **处理结果**：处理 `ResultSet` 结果集。
* **关闭资源**：在 `finally` 块中关闭 `ResultSet`, `Statement`, `Connection`。

### 依赖 (Maven)

```java
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.4.0</version>
</dependency>
```

### 示例代码

```java
import java.sql.*;

public class MysqlJdbcApplication {
    static final String DB_URL = "jdbc:mysql://localhost:3306/java_demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
    static final String USER = "lizhao";
    static final String PASS = "123456";

    public static void main(String[] args) {
        String sql = "SELECT brand, device, net, app_version, user_count, page_view FROM result WHERE brand = ? LIMIT 3";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {

            pstmt.setString(1, "Xiaomi");

            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    String brand = rs.getString("brand");
                    String device = rs.getString("device");
                    String net = rs.getString("net");
                    String app_version = rs.getString("app_version");
                    int user_count = rs.getInt("user_count");
                    int page_view = rs.getInt("page_view");

                    System.out.printf("brand=%s, device=%s, net=%s, app_version=%s, user_count=%d, page_view=%d%n",
                      brand, device, net, app_version, user_count, page_view);
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        System.out.println("Goodbye!");
    }
}
```

## XML 解析

XML 是一种广泛使用的数据交换格式。

Java 中解析 XML 的常见方式有四种：DOM、SAX、JDOM、DOM4J。

* **DOM**：将整个 XML 文档加载到内存中，构建一个树形结构。优点是**可以随机访问和修改**，缺点是对于大文件**内存消耗巨大**。
* **SAX**：基于事件驱动的解析方式，逐行读取 XML 文件。优点是**内存占用小、效率高**，缺点是**只能顺序读取，不能修改**。
* **JDOM**：Java 特有的模型，简化了 DOM 的 API，使用具体类而非接口。
* **DOM4J**：一个非常优秀的 Java XML API，提供了强大的功能和良好的性能，支持 XPath，是目前最推荐的第三方库。

DOM 和 SAX 是底层标准接口（JDK 自带），JDOM 和 DOM4J 则是基于底层 API 的高级封装，专为 Java 语言设计，提供了更便捷的 API。

### DOM（Document Object Model）

DOM 将整个 XML 文档加载到内存中，构建成一个树形结构，每个节点对应 XML 元素、属性或文本内容。

**官方标准**：XML 文档的官方 W3C 标准，跨语言、跨平台。

**DOM 优点**：随机访问文档中任意节点，支持双向导航，方便地增删改 XML 内容。

**DOM 缺点**：必须一次性加载整个文档，内存消耗大，大文件可能导致内存溢出（OutOfMemoryError）。

**适用场景**：中小型 XML 文档（几 MB 以下）、需要多次修改或随机访问的场合。

```java
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;

public class Main {
    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse("src/xml/students.xml");

        NodeList students = doc.getElementsByTagName("student");
        for (int i = 0; i < students.getLength(); i++) {
            Element student = (Element) students.item(i);
            String id = student.getAttribute("id");
            String name = student.getElementsByTagName("name").item(0).getTextContent();
            System.out.println("ID: " + id + ", Name: " + name);
        }
    }
}
```

### SAX（Simple API for XML）

SAX 采用事件驱动模型，逐行读取 XML 文档，遇到标签开始、标签结束、文本内容时触发回调事件。**内存中不保留完整文档**。

**SAX 优点**：内存消耗极小，适合处理超大 XML，解析速度快，可以边读边处理，也可以提前终止解析。

**SAX 缺点**：只能向前遍历，不能回退或随机访问，无法修改 XML 内容。

**适用场景**：超大 XML 文档（几十 MB 甚至 GB 级）、只需要读取部分数据的场合

```java
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;

public class Main extends DefaultHandler {
    private String currentTag;

    public void startElement(String uri, String localName, String qName, Attributes attrs) {
        currentTag = qName;
        if ("student".equals(qName)) {
            System.out.println("ID: " + attrs.getValue("id"));
        }
    }

    // SAX 解析器在解析 XML 文档时，会将元素标签之间的所有文本内容（包括换行符、空格、制表符等空白字符）都当作普通文本处理
    // 比如：<name>Hello</name>
    // <name> 会输出 Name: Hello，
    // </name>，会输出 Name: ，即后面的空格
    public void characters(char[] ch, int start, int length) {
        if ("name".equals(currentTag)) {
            System.out.println("Name: " + new String(ch, start, length));
        }
    }

    public static void main(String[] args) throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
        parser.parse("src/xml/students.xml", new Main());
    }
}
```

### JDOM

JDOM 是 Java 特定的文档模型，底层使用 SAX 和 DOM 技术，封装成 Java 风格的对象模型，大量使用 Java 集合类（如 List）。

**JDOM 优点**：Java 原生风格，API 简洁，代码易读。

**JDOM 缺点**：第三方库，需要额外引入依赖，功能相对 DOM4J 有限。

**适用场景**：中小型 XML 文档、传统 Java 项目。

**Maven 依赖配置：**

```xml
<dependency>
    <groupId>org.jdom</groupId>
    <artifactId>jdom2</artifactId>
    <version>2.0.6.1</version>
</dependency>
```

```java
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import java.util.List;

public class Main {
    public static void main(String[] args) throws Exception {
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build("src/xml/students.xml");
        Element root = doc.getRootElement();

        List<Element> students = root.getChildren("student");
        for (Element student : students) {
            String id = student.getAttributeValue("id");
            String name = student.getChildText("name");
            System.out.println("ID: " + id + ", Name: " + name);
        }
    }
}
```

### DOM4J（推荐）

DOM4J 在 JDOM 的基础上进一步封装，底层同时支持 DOM 和 SAX，提供了更强大的功能集和更好的性能，支持 XPath 语法。

**DOM4J 优点**：性能最优，API 设计优雅（支持链式调用），内置 XPath 支持，查询节点非常方便，功能全面。

**DOM4J 缺点**：第三方库，需要额外引入依赖，API 相对 JDOM 稍复杂（但功能更强大）。

**适用场景**：绝大多数 Java XML 处理场景，是目前业界最推荐的方式。

**Maven 依赖配置：**

```xml
<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.4</version>
</dependency>
```

```java
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.util.List;

public class Main {
    public static void main(String[] args) throws Exception {
        SAXReader reader = new SAXReader();
        Document doc = reader.read("src/xml/students.xml");
        Element root = doc.getRootElement();

        List<Element> students = root.elements("student");
        for (Element student : students) {
            String id = student.attributeValue("id");
            String name = student.elementText("name");
            System.out.println("ID: " + id + ", Name: " + name);
        }
    }
}
```

**推荐**：在大多数场景下，**DOM4J** 凭借优秀的性能、简洁的 API 和强大的 XPath 支持，是目前 Java XML 处理的最佳选择。

## 参考链接

[Java 官方文档 - w3cschool](https://www.w3cschool.cn/java/dict)

[新宝库 - Java Swing 教程](https://www.xinbaoku.com/swing/)
