Java 桌面程序开发全攻略

Java 不仅仅是为服务器端和安卓应用而生,它同样拥有强大而成熟的桌面应用开发生态,无论是构建简单的工具软件,还是复杂的跨平台客户端,Java 都能胜任。

java 桌面程序开发教程
(图片来源网络,侵删)

本教程将分为以下几个部分:

  1. 入门基础:Swing
  2. 现代选择:JavaFX
  3. 高级主题:打包、数据库、多线程
  4. 推荐学习资源

第一部分:入门基础 - Swing (Java Foundation Classes - JFC)

Swing 是 Java 最早、也是最经典的 GUI 工具包,它使用纯 Java 编写,不依赖于本地平台,因此具有“一次编写,到处运行”的跨平台特性,虽然其外观可能略显传统,但它是理解 GUI 编程核心概念的最佳起点。

核心概念

  • 组件: GUI 界面上的基本元素,如按钮、标签、文本框等,所有组件都继承自 JComponent 类。
  • 容器: 用于存放其他组件的特殊组件,常见的容器有 JFrame (主窗口)、JPanel (面板)。
  • 布局管理器: 负责决定容器中组件的位置和大小,常见的有 FlowLayout (流式布局)、BorderLayout (边界布局)、GridLayout (网格布局) 和 GridBagLayout (网格包布局,最灵活也最复杂)。
  • 事件处理: GUI 程序的核心,用户与组件交互(如点击按钮、输入文本)会触发事件,通过“监听器”来响应这些事件。

第一个 Swing 程序:Hello World

让我们创建一个最简单的窗口,上面带一个按钮。

步骤 1:创建主窗口

java 桌面程序开发教程
(图片来源网络,侵删)

JFrame 是所有 Swing GUI 程序的顶层容器。

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class FirstSwingApp {
    public static void main(String[] args) {
        // 使用 SwingUtilities.invokeLater 确保 GUI 创建在事件分发线程上
        SwingUtilities.invokeLater(() -> {
            // 1. 创建主窗口
            JFrame frame = new JFrame("我的第一个 Swing 窗口");
            // 2. 设置窗口大小 (宽 x 高)
            frame.setSize(400, 300);
            // 3. 设置窗口关闭时的默认操作 (EXIT_ON_CLOSE 表示退出程序)
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            // 4. 设置窗口居中显示
            frame.setLocationRelativeTo(null);
            // 5. 显示窗口
            frame.setVisible(true);
        });
    }
}

运行结果:你会看到一个标题为“我的第一个 Swing 窗口”的空白窗口。

代码解析

  • SwingUtilities.invokeLater(...) 是一个非常重要的方法,它确保所有与 GUI 相关的代码都在“事件分发线程”(EDT)上执行,这是线程安全的最佳实践。
  • JFrame 创建了窗口框架,但需要 setVisible(true) 才能显示出来。

步骤 2:添加组件

java 桌面程序开发教程
(图片来源网络,侵删)

现在我们在窗口里添加一个按钮和一个标签。

import javax.swing.*;
import java.awt.*;
public class SimpleApp {
    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);
            // 1. 创建一个面板作为容器,并设置布局
            JPanel panel = new JPanel();
            // 使用 FlowLayout,组件会像文本一样从左到右排列
            panel.setLayout(new FlowLayout());
            // 2. 创建组件
            JLabel label = new JLabel("你好, Swing!");
            JButton button = new JButton("点击我");
            // 3. 将组件添加到面板中
            panel.add(label);
            panel.add(button);
            // 4. 将面板添加到窗口的内容面板中
            frame.getContentPane().add(panel);
            frame.setVisible(true);
        });
    }
}

运行结果:窗口中会显示一个标签和一个按钮。

代码解析

  • 我们通常不直接把组件加到 JFrame,而是加到 JFrame 的“内容面板”(ContentPane)上。getContentPane() 方法可以获取这个面板。
  • JPanel 是一个通用的轻量级容器,我们用它来组织其他组件,并设置布局管理器。

步骤 3:处理事件

让按钮点击后,改变标签的文字。

// ... (前面的代码相同) ...
// 在 main 方法内部,创建组件后
JLabel label = new JLabel("你好, Swing!");
JButton button = new JButton("点击我");
// 1. 为按钮添加动作监听器
button.addActionListener(e -> {
    // 当按钮被点击时,这段代码会执行
    label.setText("按钮被点击了!");
});
// ... (将组件添加到面板) ...

运行结果:点击按钮,标签的文字会立即改变。

代码解析

  • addActionListener(...) 方法为按钮注册了一个“动作监听器”。
  • 我们使用了 Java 8 的 Lambda 表达式 e -> {...} 来简化匿名内部类的写法。eActionEvent 对象,包含了事件的相关信息。

常用 Swing 组件一览

组件名称 类名 用途
按钮 JButton 触发一个动作
JLabel 显示文本或图标
文本框 JTextField 单行文本输入
密码框 JPasswordField 单行密码输入,显示为掩码
文本区域 JTextArea 多行文本输入/显示
复选框 JCheckBox 选择一个或多个选项
单选框 JRadioButton 在一组选项中只选一个
下拉框 JComboBox 提供一个下拉列表供用户选择
列表框 JList 显示一个可滚动的项目列表
表格 JTable 以表格形式显示数据
JTree 以分层结构显示数据

第二部分:现代选择 - JavaFX

Swing 虽然经典,但已经显得有些陈旧,JavaFX 是 Oracle 官方推出的新一代 GUI 工具包,它拥有更现代的视觉效果、更强大的 UI 控件(如 WebView 内嵌浏览器、Chart 图表)以及基于 CSS 的样式化能力。

重要提示:从 Java 11 开始,JavaFX 已经不再包含在 JDK 中,需要单独下载。

JavaFX 与 Swing 的核心区别

  • 架构:Swing 是 AWT 的轻量级版本,依赖重量级容器,JavaFX 是一个全新的、完全基于图形硬件加速的框架。
  • 语言:Swing 使用 Java 代码来构建 UI,JavaFX 推荐使用 FXML (一种基于 XML 的标记语言) 来描述 UI,将 UI 逻辑与业务逻辑分离,类似于网页开发中的 HTML 和 JavaScript。
  • 样式:Swing 的样式定制能力有限,JavaFX 支持使用 CSS 来美化界面,非常灵活。
  • 功能:JavaFX 提供了内置的图表、动画、媒体播放等高级功能,而 Swing 通常需要第三方库。

第一个 JavaFX 程序:Hello World

步骤 1:设置项目环境

如果你使用 Maven,在 pom.xml 中添加 JavaFX 依赖:

<dependencies>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>21</version> <!-- 使用你想要的版本 -->
    </dependency>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-fxml</artifactId>
        <version>21</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-maven-plugin</artifactId>
            <version>0.0.8</version>
            <executions>
                <execution>
                    <!-- Default configuration for running with: mvn javafx:run -->
                    <id>default-cli</id>
                    <configuration>
                        <mainClass>com.example.MainApp</mainClass>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

步骤 2:编写主程序

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloJavaFX extends Application {
    @Override
    public void start(Stage primaryStage) {
        // 1. 创建一个按钮
        Button btn = new Button();
        btn.setText("点击我");
        // 2. 为按钮添加事件处理器
        btn.setOnAction(event -> {
            System.out.println("Hello World!");
            btn.setText("你好, JavaFX!");
        });
        // 3. 创建一个布局容器,并将按钮放入其中
        StackPane root = new StackPane();
        root.getChildren().add(btn);
        // 4. 创建一个场景,并将布局放入场景中
        Scene scene = new Scene(root, 300, 250);
        // 5. 将场景设置到舞台(窗口)上
        primaryStage.setTitle("Hello JavaFX");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    public static void main(String[] args) {
        // 启动 JavaFX 应用程序
        launch(args);
    }
}

运行结果:与 Swing 版本的 Hello World 类似,点击按钮后控制台打印信息,按钮文字改变。

JavaFXML 示例(推荐方式)

这是 JavaFX 的标准开发模式。

步骤 1:创建 FXML 文件 (hello-view.fxml)

src/main/resources 目录下创建 hello-view.fxml 文件。

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.HelloController">
    <children>
        <Label text="欢迎使用 JavaFXML!" />
        <Button text="点击我" onAction="#handleButtonClick" />
    </children>
</VBox>
  • VBox: 垂直布局的容器。
  • fx:controller: 指定与此 UI 关联的控制器类。
  • onAction="#handleButtonClick": 指定按钮点击时调用的控制器方法名。

步骤 2:创建控制器类 (HelloController.java)

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
public class HelloController {
    @FXML
    private Label welcomeText; // FXML 中的组件需要用 @FXML 注解
    @FXML
    private Button helloButton;
    @FXML
    protected void onHelloButtonClick() {
        welcomeText.setText("欢迎来到 JavaFXML 世界!");
    }
    // 或者使用 FXML 中指定的方法名
    @FXML
    protected void handleButtonClick() {
        welcomeText.setText("按钮被点击了!");
    }
}
  • @FXML: 注解告诉 FXML 加载器,这个字段或方法需要从 FXML 文件中注入。
  • 方法名必须与 FXML 文件中 onAction 属性指定的值匹配(去掉 )。

步骤 3:修改主程序以加载 FXML

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class MainApp extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(MainApp.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 320, 240);
        stage.setTitle("JavaFXML 应用");
        stage.setScene(scene);
        stage.show();
    }
    public static void main(String[] args) {
        launch();
    }
}

第三部分:高级主题

打包你的应用

  • Swing: 可以使用 Maven Assembly PluginMaven Shade Plugin 将所有依赖打包成一个可执行的 JAR 文件。
  • JavaFX: 强烈推荐使用 jpackage 工具,它可以直接将你的 JavaFX 应用打包成原生安装包(如 .exe, .msi, .dmg, .deb),用户无需安装 Java 运行环境即可运行。

使用 jpackage 打包示例:

# 假设你的主类是 com.example.MainApp
# 并且你有一个名为 "HelloJavaFX" 的模块 (在 module-info.java 中定义)
jpackage --type exe --name MyJavaFXApp --input target/libs --main-jar MyJavaFXApp.jar --main-class com.example.MainApp --module-path "C:/path/to/javafx-sdk-21/lib" --add-modules javafx.controls,javafx.fxml

连接数据库

桌面应用通常需要持久化数据,最常用的方式是连接关系型数据库。

技术栈:

  • 数据库: SQLite (轻量级,无需安装服务器), MySQL, PostgreSQL 等。
  • JDBC 驱动: 每种数据库都有其对应的 JDBC 驱动。
  • 连接池: 如 HikariCP,用于高效管理数据库连接。

示例代码 (使用 SQLite 和 JDBC):

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DatabaseExample {
    private static final String DB_URL = "jdbc:sqlite:mydatabase.db";
    public static void main(String[] args) {
        // 1. 加载驱动 (对于 JDBC 4.0+,通常可以省略)
        try {
            Class.forName("org.sqlite.JDBC");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }
        // 2. 建立连接
        try (Connection conn = DriverManager.getConnection(DB_URL)) {
            if (conn != null) {
                System.out.println("已成功连接到数据库。");
                // 3. 创建表 (如果不存在)
                String sql = "CREATE TABLE IF NOT EXISTS users (\n" +
                        " id integer PRIMARY KEY,\n" +
                        " name text NOT NULL,\n" +
                        " email text NOT NULL UNIQUE\n" +
                        ");";
                try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                    pstmt.execute();
                }
                // 4. 插入数据
                sql = "INSERT INTO users(name, email) VALUES(?,?)";
                try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                    pstmt.setString(1, "张三");
                    pstmt.setString(2, "zhangsan@example.com");
                    pstmt.executeUpdate();
                }
                // 5. 查询数据
                sql = "SELECT id, name, email FROM users";
                try (PreparedStatement pstmt = conn.prepareStatement(sql);
                     ResultSet rs = pstmt.executeQuery()) {
                    while (rs.next()) {
                        System.out.println(rs.getInt("id") +  "\t" +
                                           rs.getString("name") + "\t" +
                                           rs.getString("email"));
                    }
                }
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

多线程与 GUI

绝对禁止在事件分发线程上执行耗时操作!(如网络请求、文件 I/O、复杂计算),否则会导致整个 GUI 界面“卡死”(无响应)。

正确做法:使用 SwingWorker (Swing) 或 Task (JavaFX)。

JavaFX Task 示例:

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ThreadExample extends Application {
    private Label statusLabel = new Label("准备就绪");
    @Override
    public void start(Stage primaryStage) {
        Button startButton = new Button("开始耗时任务");
        startButton.setOnAction(e -> startLongTask());
        VBox root = new VBox(10, statusLabel, startButton);
        Scene scene = new Scene(root, 300, 200);
        primaryStage.setTitle("多线程示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    private void startLongTask() {
        // 1. 创建一个 Task
        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                // 这是后台线程,可以安全地执行耗时操作
                for (int i = 1; i <= 10; i++) {
                    // 更新进度
                    updateProgress(i, 10);
                    // 更新消息
                    updateMessage("正在处理: " + i * 10 + "%");
                    // 模拟耗时工作
                    Thread.sleep(500);
                }
                return null;
            }
        };
        // 2. 监听任务状态变化,更新 GUI
        task.messageProperty().addListener((obs, oldVal, newVal) -> {
            // 这段代码会在 EDT 上执行
            statusLabel.setText(newVal);
        });
        // 3. 在新线程中执行任务
        Thread thread = new Thread(task);
        thread.setDaemon(true); // 设置为守护线程,这样当主程序结束时,它也会结束
        thread.start();
    }
    public static void main(String[] args) {
        launch(args);
    }
}

第四部分:推荐学习资源

  1. 官方文档:

  2. 在线教程和课程:

    • Udemy / Coursera: 搜索 "Java Swing", "JavaFX" 等关键词,有很多高质量的实战课程。
    • Baeldung: 提供了大量关于 JavaFX 和 Swing 的简短、 focused 的文章和示例。
    • YouTube: "Java GUI Tutorial", "JavaFX Tutorial" 等关键词下有大量视频教程,适合初学者。
  3. 社区和问答:

    • Stack Overflow: 解决具体问题的最佳去处,提问时记得加上 swing, javafx, java-gui 等标签。

总结与建议

  • 如果你是初学者,或者只需要开发一个简单的、对界面美观度要求不高的工具:从 Swing 开始是不错的选择,它能让你快速理解 GUI 编程的核心思想。
  • 如果你正在开发一个新的、现代化的桌面应用,或者对 UI 效果、功能有较高要求强烈推荐使用 JavaFX,它是 Java 桌面应用的未来,拥有更强大的功能和更优秀的开发体验。
  • 无论选择哪个框架,请务必遵循 事件分发线程 的原则,并学会使用 FXML (JavaFX) 或 MVC 模式来组织你的代码,这将使你的项目更易于维护和扩展。