【项目三】用GUI编程实现局域网群聊软件

今天我们先了解StringBuilderBigDecimalDateLocalDateTime这些前置核心技术,再手把手带你开发一个「局域网群聊软件」。


目录

一、前置核心技术:

[1. 字符串拼接神器:StringBuilder](#1. 字符串拼接神器:StringBuilder)

[为什么需要 StringBuilder?](#为什么需要 StringBuilder?)

核心用法

[2. 高精度计算:BigDecimal](#2. 高精度计算:BigDecimal)

[为什么需要 BigDecimal?](#为什么需要 BigDecimal?)

核心用法

[3. 日期时间处理:Date vs LocalDateTime](#3. 日期时间处理:Date vs LocalDateTime)

[① 传统日期类:Date(不推荐)](#① 传统日期类:Date(不推荐))

[② 现代日期类:LocalDateTime(推荐)](#② 现代日期类:LocalDateTime(推荐))

二、实战项目:局域网群聊软件

[1. 项目需求](#1. 项目需求)

[2. 技术选型](#2. 技术选型)

[3. 核心架构](#3. 核心架构)

[4. 分步实现](#4. 分步实现)

第一步:登录界面

第二步:聊天界面

第三步:服务端核心逻辑

第四步:客户端通信逻辑

[5. 运行项目](#5. 运行项目)

三、总结


一、前置核心技术:

1. 字符串拼接神器:StringBuilder

为什么需要 StringBuilder?

Java 中的**String** 是不可变对象 ,每次拼接字符串(比如用+)都会创建新对象,频繁拼接时性能极低。而StringBuilder是可变的字符序列,专门解决字符串拼接效率问题。

核心用法
方法 作用
append() 追加字符串 / 数字 / 对象等
toString() 转换为最终的 String

入门示例

java 复制代码
// 拼接用户消息(项目中会用到)
public class StringBuilderDemo {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        // 拼接发送者、时间、消息内容
        sb.append("张三")
          .append(" 2024-08-08 10:00:00")
          .append("\n")
          .append("大家好呀~")
          .append("\n");
        // 转换为最终字符串
        String msg = sb.toString();
        System.out.println(msg);
    }
}

2. 高精度计算:BigDecimal

为什么需要 BigDecimal?

float 和**double** 会有精度丢失问题(比如0.1 + 0.2 ≠ 0.3),涉及金额、精准计算时必须用BigDecimal

核心用法
方法 作用
BigDecimal(String val) 构造方法(推荐用字符串入参)
add() 加法
subtract() 减法
multiply() 乘法
divide() 除法(需指定精度)

入门示例

java 复制代码
import java.math.BigDecimal;

public class BigDecimalDemo {
    public static void main(String[] args) {
        // 避免用double入参,防止精度丢失
        BigDecimal a = new BigDecimal("0.1");
        BigDecimal b = new BigDecimal("0.2");
        
        // 加法
        BigDecimal sum = a.add(b);
        System.out.println("0.1 + 0.2 = " + sum); // 输出0.3
        
        // 除法(指定保留2位小数,四舍五入)
        BigDecimal c = new BigDecimal("10");
        BigDecimal d = new BigDecimal("3");
        BigDecimal divide = c.divide(d, 2, BigDecimal.ROUND_HALF_UP);
        System.out.println("10 ÷ 3 = " + divide); // 输出3.33
    }
}

3. 日期时间处理:Date vs LocalDateTime

① 传统日期类:Date(不推荐)

Date是 Java 早期的日期类,存在线程不安全、API 设计混乱(比如月份从 0 开始)等问题,仅做了解即可。

简单示例

java 复制代码
import java.util.Date;

public class DateDemo {
    public static void main(String[] args) {
        // 获取当前时间
        Date now = new Date();
        System.out.println("当前时间(Date):" + now); // 格式不直观
    }
}
② 现代日期类:LocalDateTime(推荐)

Java 8 推出的LocalDateTime解决了Date的所有痛点,线程安全、API 清晰、支持格式化,是开发首选。

核心用法

方法 作用
LocalDateTime.now() 获取当前时间
DateTimeFormatter.ofPattern() 定义时间格式
format() 格式化时间为字符串

入门示例:

java 复制代码
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class LocalDateTimeDemo {
    public static void main(String[] args) {
        // 1. 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        
        // 2. 定义格式化规则:年-月-日 时:分:秒 星期 上/下午
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");
        
        // 3. 格式化时间(项目中用于拼接消息时间)
        String nowStr = dtf.format(now);
        System.out.println("格式化后的时间:" + nowStr); 
        // 输出示例:2024-08-08 10:15:30 周四 上午
    }
}

二、实战项目:局域网群聊软件

学会了前置技术,我们用一个「局域网群聊软件」把知识落地!这个项目能实现:输入昵称登录、实时显示在线人数、群聊消息即时发送 / 接收。

1. 项目需求

  • 登录界面:仅需输入昵称即可登录;
  • 群聊界面:展示在线人数、消息展示框、消息输入框、发送按钮;
  • 核心功能:实时更新在线人数、群聊消息即时通讯。

2. 技术选型

  • GUI 界面:Swing(Java 内置的图形界面工具,入门友好);
  • 网络通信:Socket(实现客户端与服务端的 TCP 通信);
  • 基础工具:前面学的LocalDateTime(格式化消息时间)、StringBuilder(拼接消息内容)。

3. 核心架构

整个项目分为「服务端」和「客户端」两部分:

  • 服务端:监听客户端连接、存储在线用户、转发群聊消息、更新在线人数;
  • 客户端:登录界面、聊天界面、与服务端通信(发送 / 接收消息)。

4. 分步实现

第一步:登录界面

**功能:**输入昵称,点击「进入」连接服务端,点击「取消」退出程序。

java 复制代码
package com.itheima.ui;

import javax.swing.*;
import java.awt.*;

public class ChatEntryFrame extends JFrame {
    private JTextField nicknameField; // 昵称输入框
    private JButton enterButton; // 进入按钮
    private JButton cancelButton; // 取消按钮

    public ChatEntryFrame() {
        // 窗口基础设置
        setTitle("局域网聊天室-登录");
        setSize(350, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null); // 窗口居中
        setResizable(false); // 禁止调整大小

        // 布局设置(顶部:昵称输入;底部:按钮)
        JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.setBackground(Color.decode("#F0F0F0"));
        add(mainPanel);

        // 顶部:昵称输入区域
        JPanel topPanel = new JPanel();
        JLabel nicknameLabel = new JLabel("昵称:");
        nicknameLabel.setFont(new Font("楷体", Font.BOLD, 16));
        nicknameField = new JTextField(10);
        nicknameField.setFont(new Font("楷体", Font.PLAIN, 16));
        topPanel.add(nicknameLabel);
        topPanel.add(nicknameField);
        mainPanel.add(topPanel, BorderLayout.NORTH);

        // 底部:按钮区域
        JPanel buttonPanel = new JPanel();
        enterButton = new JButton("进入");
        enterButton.setBackground(Color.decode("#007BFF"));
        enterButton.setForeground(Color.WHITE);
        cancelButton = new JButton("取消");
        cancelButton.setBackground(Color.decode("#DC3545"));
        cancelButton.setForeground(Color.WHITE);
        buttonPanel.add(enterButton);
        buttonPanel.add(cancelButton);
        mainPanel.add(buttonPanel, BorderLayout.SOUTH);

        // 按钮事件:进入聊天室
        enterButton.addActionListener(e -> {
            String nickname = nicknameField.getText();
            if (!nickname.isEmpty()) {
                dispose(); // 关闭登录窗口
                // 连接服务端+打开聊天界面(后续补充)
                new ClientChatFrame(nickname); 
            } else {
                JOptionPane.showMessageDialog(this, "请输入昵称!");
            }
        });

        // 按钮事件:取消退出
        cancelButton.addActionListener(e -> System.exit(0));

        this.setVisible(true);
    }

    public static void main(String[] args) {
        new ChatEntryFrame(); // 启动登录界面
    }
}
第二步:聊天界面

**功能:**展示消息、输入消息、显示在线人数、发送消息。

java 复制代码
package com.itheima.ui;

import javax.swing.*;
import java.awt.*;

public class ClientChatFrame extends JFrame {
    public JTextArea smsContent = new JTextArea(23, 50); // 消息展示框
    private JTextArea smsSend = new JTextArea(4, 40); // 消息输入框
    public JList<String> onLineUsers = new JList<>(); // 在线用户列表
    private JButton sendBn = new JButton("发送"); // 发送按钮

    public ClientChatFrame(String nickname) {
        initView();
        // 后续补充:连接服务端、接收/发送消息的逻辑
        this.setVisible(true);
    }

    private void initView() {
        // 窗口基础设置
        this.setTitle("局域网聊天室-群聊");
        this.setSize(700, 600);
        this.setLayout(new BorderLayout());
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);

        // 消息展示框:不可编辑、带滚动条
        smsContent.setEditable(false);
        smsContent.setBackground(new Color(0xdd, 0xdd, 0xdd));
        JScrollPane smsContentScrollPane = new JScrollPane(smsContent);
        this.add(smsContentScrollPane, BorderLayout.CENTER);

        // 底部:消息输入+发送按钮
        JPanel bottomPanel = new JPanel(new BorderLayout());
        JScrollPane smsSendScrollPane = new JScrollPane(smsSend);
        bottomPanel.add(smsSendScrollPane, BorderLayout.CENTER);
        bottomPanel.add(sendBn, BorderLayout.EAST);
        this.add(bottomPanel, BorderLayout.SOUTH);

        // 右侧:在线用户列表
        JScrollPane userListScrollPane = new JScrollPane(onLineUsers);
        userListScrollPane.setPreferredSize(new Dimension(120, 500));
        this.add(userListScrollPane, BorderLayout.EAST);

        // 发送按钮事件(后续补充:发送消息到服务端)
        sendBn.addActionListener(e -> {
            String msg = smsSend.getText();
            if (!msg.isEmpty()) {
                // 发送消息逻辑(后续补充)
                smsSend.setText(""); // 清空输入框
            }
        });
    }
}
第三步:服务端核心逻辑

**功能:**监听端口、接收客户端连接、处理登录 / 群聊消息、转发消息、更新在线人数。

java 复制代码
package com.itheima;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class Server {
    // 存储在线用户:key=客户端Socket,value=昵称
    public static final Map<Socket, String> onLineSockets = new HashMap<>();
    private static final int PORT = 8888; // 监听端口

    public static void main(String[] args) {
        System.out.println("服务端启动中...");
        try {
            ServerSocket serverSocket = new ServerSocket(PORT);
            // 循环监听客户端连接
            while (true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept(); // 接收客户端连接
                new ServerReaderThread(socket).start(); // 开启线程处理该客户端
                System.out.println("客户端连接成功:" + socket.getInetAddress());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 处理客户端消息的线程类
    static class ServerReaderThread extends Thread {
        private Socket socket;

        public ServerReaderThread(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                DataInputStream dis = new DataInputStream(socket.getInputStream());
                // 循环接收客户端消息(1=登录,2=群聊)
                while (true) {
                    int type = dis.readInt();
                    switch (type) {
                        case 1:
                            // 处理登录:接收昵称,更新在线列表
                            String nickname = dis.readUTF();
                            onLineSockets.put(socket, nickname);
                            updateOnLineUserList(); // 更新所有客户端的在线人数
                            break;
                        case 2:
                            // 处理群聊:接收消息,转发给所有在线客户端
                            String msg = dis.readUTF();
                            sendMsgToAll(nickname, msg); // 转发消息
                            break;
                    }
                }
            } catch (Exception e) {
                // 客户端下线:移除socket,更新在线列表
                System.out.println("客户端下线:" + socket.getInetAddress());
                onLineSockets.remove(socket);
                updateOnLineUserList();
            }
        }

        // 给所有在线客户端转发群聊消息(用到StringBuilder+LocalDateTime)
        private void sendMsgToAll(String nickname, String msg) {
            // 1. 拼接消息(昵称+时间+内容)
            StringBuilder sb = new StringBuilder();
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            sb.append(nickname)
              .append(" [")
              .append(dtf.format(now))
              .append("]: \n")
              .append(msg)
              .append("\n");
            String sendMsg = sb.toString();

            // 2. 转发给所有在线客户端
            for (Socket s : onLineSockets.keySet()) {
                try {
                    DataOutputStream dos = new DataOutputStream(s.getOutputStream());
                    dos.writeInt(2); // 标记为群聊消息
                    dos.writeUTF(sendMsg);
                    dos.flush();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        // 更新所有客户端的在线用户列表
        private void updateOnLineUserList() {
            Collection<String> names = onLineSockets.values();
            for (Socket s : onLineSockets.keySet()) {
                try {
                    DataOutputStream dos = new DataOutputStream(s.getOutputStream());
                    dos.writeInt(1); // 标记为在线列表消息
                    dos.writeInt(names.size()); // 在线人数
                    for (String name : names) {
                        dos.writeUTF(name); // 逐个发送昵称
                    }
                    dos.flush();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
第四步:客户端通信逻辑

**功能:**连接服务端、发送登录 / 群聊消息、接收服务端的在线列表 / 群聊消息。

java 复制代码
// 补充到ClientChatFrame类中
private Socket socket;
private DataOutputStream dos;

public ClientChatFrame(String nickname) {
    initView();
    // 连接服务端
    try {
        socket = new Socket("127.0.0.1", 8888); // 本地测试用127.0.0.1
        dos = new DataOutputStream(socket.getOutputStream());
        // 发送登录消息(类型1+昵称)
        dos.writeInt(1);
        dos.writeUTF(nickname);
        dos.flush();
        // 开启线程接收服务端消息
        new ClientReaderThread().start();
    } catch (Exception e) {
        JOptionPane.showMessageDialog(this, "连接服务端失败!");
        e.printStackTrace();
    }
    this.setVisible(true);
}

// 接收服务端消息的线程
class ClientReaderThread extends Thread {
    @Override
    public void run() {
        try {
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            while (true) {
                int type = dis.readInt();
                switch (type) {
                    case 1:
                        // 接收在线列表:更新右侧用户列表
                        int size = dis.readInt();
                        String[] names = new String[size];
                        for (int i = 0; i < size; i++) {
                            names[i] = dis.readUTF();
                        }
                        onLineUsers.setListData(names); // 更新列表
                        break;
                    case 2:
                        // 接收群聊消息:展示到消息框
                        String msg = dis.readUTF();
                        smsContent.append(msg); // 追加消息
                        break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 补充发送按钮的事件(发送群聊消息)
sendBn.addActionListener(e -> {
    String msg = smsSend.getText();
    if (!msg.isEmpty()) {
        try {
            dos.writeInt(2); // 标记为群聊消息
            dos.writeUTF(msg);
            dos.flush();
            smsSend.setText(""); // 清空输入框
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "发送消息失败!");
        }
    }
});

5. 运行项目

  1. 先启动Server类(服务端);
  2. 启动ChatEntryFrame类(客户端登录界面);
  3. 输入昵称,点击「进入」,打开聊天界面;
  4. 启动多个客户端,即可实现群聊和在线人数实时更新。

三、总结

  1. 掌握了StringBuilder(高效拼接)、BigDecimal(精准计算)、LocalDateTime(日期处理)这些 Java 核心工具类;
  2. Swing+Socket 开发了一个简易的局域网群聊软件,把基础类落地到实战中。
相关推荐
无名-CODING2 小时前
Java 爬虫高级技术:反反爬策略与分布式爬虫实战
java·分布式·爬虫
jonyleek2 小时前
JVS物联网应用中控制器的四大职责和设备接入全流程
java·struts·servlet·私有化部署
csdn2015_2 小时前
java 将 List<Map<String,Object>> 类型里面的值转换为List<String>
java·windows·list
怀化纱厂球迷2 小时前
android车载应用动画-仿窗帘式下拉显示!Android 实现跟手裁剪动画 + RecyclerView 列表展示
android·java
牢七2 小时前
jfinal_cms-v5.1.0 白盒 nday
开发语言·python
ayt0072 小时前
Netty 4.2核心类解析:SingleThreadIoEventLoop的设计哲学与实现
java·网络
无名-CODING2 小时前
Java 爬虫进阶:动态网页、多线程与 WebMagic 框架实战
java·爬虫·okhttp
weixin_704266052 小时前
Spring 注解驱动开发与 Spring Boot 核心知识点梳理
java·spring boot·spring
开开心心就好2 小时前
伪装文件历史记录!修改时间的黑科技软件
java·前端·科技·r语言·edge·pdf·语音识别