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并启动。 - 调用
FutureTask的get()方法获取返回值(此方法会阻塞直到线程结束)。
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
ReentrantLock 是 Lock 接口的实现,它提供了比 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");
}
}
注意事项:
- 不要使用优先级解决竞争条件:它不可靠,正确的做法是使用
synchronized、Lock等同步工具。 - 优先级继承:如果一个高优先级线程因等待锁而被阻塞,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("所有线程都已结束");
}
}
如果多个线程需要访问同一个变量(如计数器、缓存),必须使用同步机制(synchronized、Lock、volatile 等)保证数据一致性。
发送邮件(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 的事件模型和布局管理器(如 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 类。
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(顶层容器)。
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。
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创建Statement或PreparedStatement对象。 - 执行 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 处理的最佳选择。