Java高级篇

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

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

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

线程与并发

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

核心概念:

  • 程序:是一段静态的代码,存储在磁盘上,是指令的集合。
  • 进程:是程序的一次动态执行过程,每个进程都有独立的内存空间和系统资源。
  • 线程:是进程中的一个执行单元,是 CPU 调度和分配的基本单位。一个进程可以包含多个线程,它们共享进程的内存空间,这使得线程间的通信更加容易-1

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

创建线程

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

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

核心步骤

  • 定义一个类实现 Runnable 接口。
  • 实现 run() 方法,编写线程执行体。
  • 创建 Thread 对象,将 Runnable 实例作为构造参数传入。
  • 调用 Thread 对象的 start() 方法启动线程。
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() 方法启动线程。
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 并启动。
  • 调用 FutureTaskget() 方法获取返回值(此方法会阻塞直到线程结束)。
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 可以修饰方法或代码块,保证同一时刻只有一个线程能执行该段代码。

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

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

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++ 这类读-改-写操作。

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)可能忽略优先级设置,将所有线程视为相同优先级。
  • 不要依赖优先级来控制程序逻辑,应使用同步机制(如锁、等待/通知等)。
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");
    }
}

注意事项:

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

多线程的使用

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

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

注意:多线程能大幅提升程序性能,但也会引入复杂性(死锁、竞态条件、内存可见性等)。

判断线程是否结束:

  • Thread.join():让当前线程等待指定线程死亡。
  • Thread.isAlive():检查线程是否处于活动状态。
  • CountDownLatch:等待多个线程完成任务。
  • ExecutorService + Future:提交任务后通过 Future.get() 阻塞等待结果。
  • ExecutorService.awaitTermination():等待线程池中所有任务完成。
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("所有线程都已结束");
    }
}

如果多个线程需要访问同一个变量(如计数器、缓存),必须使用同步机制synchronizedLockvolatile 等)保证数据一致性。

发送邮件(QQ)

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

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

依赖 (Maven)

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

示例代码

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 的事件模型和布局管理器(如 BorderLayoutFlowLayout 等)。

Swing 类库结构

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

MVC 设计模式:

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

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

Swing 核心组件:

  • 顶层容器JFrame(主窗口)、JDialog(对话框)、JApplet(嵌入网页的 Applet,现已过时)。一个 GUI 程序必须至少有一个顶层容器。
  • 中间容器JPanelJScrollPaneJTabbedPaneJToolBar 等。它们不能独立显示,必须添加到顶层容器或其他容器中。
  • 基本控件JButtonJLabelJTextFieldJCheckBoxJRadioButtonJComboBox 等。

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

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 会扩张占用其空间。如果同一区域添加多个组件,只有最后添加的那个会显示。

默认用于JFrameJDialogJWindow(顶层容器)。

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)

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

默认用于JPanelJApplet

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)

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

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 内部也使用了类似概念)。

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:组件的外边距。
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)一起使用。

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:处理焦点得失事件。
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);
        });
    }
}

示例代码

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 并退出 同样
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();
        }
    }
}
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 后退出 同样
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);
            }
        }
    }
}
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 类用于表示统一资源定位符,可以打开网络连接并读取资源(如网页、文件等)。

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 接口

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 创建 StatementPreparedStatement 对象。
  • 执行 SQL:执行 executeQuery()(查询)或 executeUpdate()(增删改)方法。
  • 处理结果:处理 ResultSet 结果集。
  • 关闭资源:在 finally 块中关闭 ResultSet, Statement, Connection

依赖 (Maven)

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

示例代码

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 以下)、需要多次修改或随机访问的场合。

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 级)、只需要读取部分数据的场合

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 依赖配置:

<dependency>
    <groupId>org.jdom</groupId>
    <artifactId>jdom2</artifactId>
    <version>2.0.6.1</version>
</dependency>
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 依赖配置:

<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.4</version>
</dependency>
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

新宝库 - Java Swing 教程

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-05-17 01:51:12

results matching ""

    No results matching ""