导读:本期聚焦于猫儿创作的《Java Swing 如何实现信用卡号输入框:聚焦显示全部,失焦只显示后四位?》,敬请观看详情。在支付类桌面应用的表单设计中,信用卡号通常需要在编辑状态展示完整号码,而在非编辑状态仅显示后四位以保护隐私。本文介绍如何基于 Java Swing 实现这样一种智能输入框,核心思路是在 JTextField 上注册 FocusListener,聚焦时恢复完整卡号,失焦时将内容替换为掩码文本,同时借助 JPasswordField、DocumentFilter 与自定义格式化输入来限制非法字符。文中给出了完整的可运行代码示例,并分析了光标位置保持、卡号分组显示、校验等常见细节问题,帮助你打造更安全也更友好的卡号录入体验。

在涉及支付的桌面客户端里,信用卡号属于敏感信息。直接把 16 位完整卡号一直显示在界面上,既不安全,也不美观。业界常见的做法是:用户点击输入框准备编辑时,显示完整卡号方便核对;一旦焦点离开输入框,就自动把内容变成类似 **** **** **** 1234 的掩码形式,只露出后四位。本文将详细讲解如何用 Java Swing 实现这样一个输入框,并顺带处理好输入过滤、格式化显示和失焦还原等细节。

Java Swing 如何实现信用卡号输入框:聚焦显示全部,失焦只显示后四位?

一、核心思路:用 FocusListener 切换显示状态

实现这个效果的关键在于 Swing 提供的焦点事件机制。FocusListener 接口包含两个方法:focusGained 在组件获得焦点时触发,focusLost 在组件失去焦点时触发。我们可以维护一个真实卡号变量,获得焦点时把它写入文本框,失去焦点时把文本框内容替换为掩码字符串,同时判断是否需要清除已输入的值。

这里有一个容易被忽略的陷阱:如果把掩码文本直接写回文本框,那么下次获得焦点时,你拿到的将是掩码文本而不是真实卡号。因此必须把真实卡号单独保存在一个成员变量中,文本框只负责展示。下面是一个最基础的实现:

import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;

public class CardNumberField extends JFrame {

    // 保存真实的卡号,文本框只负责展示
    private String realCardNumber = "";
    private final JTextField cardField = new JTextField(20);

    public CardNumberField() {
        setTitle("信用卡号输入示例");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new FlowLayout());

        cardField.setText("请输入卡号");
        cardField.setHorizontalAlignment(JTextField.CENTER);

        cardField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                // 聚焦时显示真实卡号
                cardField.setText(realCardNumber);
                cardField.setCaretPosition(cardField.getText().length());
            }

            @Override
            public void focusLost(FocusEvent e) {
                // 失焦时保存真实值并显示掩码
                String input = cardField.getText().trim();
                if (input.matches("\\d{12,19}")) {
                    realCardNumber = input;
                    cardField.setText(maskCardNumber(realCardNumber));
                } else {
                    realCardNumber = "";
                    cardField.setText("");
                }
            }
        });

        add(new JLabel("信用卡号:"));
        add(cardField);
        setSize(400, 120);
        setLocationRelativeTo(null);
    }

    // 生成 **** **** **** 1234 形式的掩码
    private String maskCardNumber(String card) {
        String prefix = "*".repeat(card.length() - 4);
        return prefix + card.substring(card.length() - 4);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new CardNumberField().setVisible(true));
    }
}

运行这段代码后,点击输入框可以正常输入数字,点击其他地方让输入框失焦,内容就会变成一串星号加上后四位。再次聚焦时又能看到完整卡号。这个基础版本已经能满足核心需求,但离生产可用还有距离,下面继续完善。

二、限制输入:只允许数字并自动分组

真实的信用卡号输入框不应该接受字母和符号。Swing 中控制输入内容的正规做法是自定义 DocumentFilter,它会在文档内容发生改变之前拦截插入和删除操作。相比在 keyTyped 事件里过滤,DocumentFilter 能同时拦截键盘输入和程序化的 setText 调用,可靠性更高。

另外一个体验优化是自动分组。信用卡号通常每四位一组显示,例如 4111 1111 1111 1111。分组只是视觉呈现,内部存储仍应保存纯数字。下面的过滤器会在用户输入时动态插入空格:

import javax.swing.text.*;

public class CardNumberDocumentFilter extends DocumentFilter {

    private static final int MAX_DIGITS = 19; // 最长支持 19 位

    @Override
    public void insertString(FilterBypass fb, int offset, String text,
                             AttributeSet attrs) throws BadLocationException {
        replace(fb, offset, 0, text, attrs);
    }

    @Override
    public void replace(FilterBypass fb, int offset, int length, String text,
                        AttributeSet attrs) throws BadLocationException {
        // 只保留数字
        StringBuilder digits = new StringBuilder();
        for (char c : text.toCharArray()) {
            if (Character.isDigit(c)) {
                digits.append(c);
            }
        }
        if (digits.length() == 0 && length == 0) {
            return;
        }

        // 取出替换前的完整文本,重新计算格式化结果
        String current = fb.getDocument().getText(0, fb.getDocument().getLength());
        String before = current.substring(0, offset);
        String after = current.substring(offset + length);
        String rawDigits = (before + digits + after).replaceAll("\\s", "");

        if (rawDigits.length() > MAX_DIGITS) {
            return; // 超出长度直接拒绝
        }

        String formatted = groupByFour(rawDigits);
        fb.replace(0, fb.getDocument().getLength(), formatted, attrs);
    }

    // 每 4 位插入一个空格
    private String groupByFour(String digits) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < digits.length(); i++) {
            if (i > 0 && i % 4 == 0) {
                sb.append(' ');
            }
            sb.append(digits.charAt(i));
        }
        return sb.toString();
    }
}

挂载方式很简单,拿到文本框的 PlainDocument 并调用 setDocumentFilter 即可:

AbstractDocument doc = (AbstractDocument) cardField.getDocument();
doc.setDocumentFilter(new CardNumberDocumentFilter());

需要注意的是,失焦时生成的掩码文本包含空格和星号,如果掩码文本也是通过带过滤器的路径写入的,就会被过滤器拒绝。解决办法是在写入掩码前判断当前是否处于失焦处理流程,用一个布尔标志临时绕过过滤器,或者直接操作 realCardNumber 变量,聚焦时再写入格式化后的数字。后者实现更简单,也是推荐做法。

三、细节打磨:光标、复制与卡号校验

第一是光标位置。聚焦时如果直接 setText,光标默认停留在开头,用户续输会很别扭。上面的代码已经用 setCaretPosition 把光标移到末尾。更好的体验是记住失焦前的光标位置,聚焦时恢复到原来的地方,特别是用户在中途修改某一位数字时。

第二是复制行为。出于安全考虑,很多支付产品禁止从卡号框复制完整卡号。如果希望复制的也是掩码文本,无需额外处理;如果希望禁止复制,可以重写 copy 方法或者使用 JPasswordFieldJPasswordFieldgetText 已被标记为不推荐,应改用 getPassword 返回字符数组,用完及时清空,避免敏感信息长期驻留内存。

第三是卡号合法性校验。可以在失焦时顺带做一次 Luhn 校验,它几乎被所有主流卡组织采用,实现只需要几行代码:

// Luhn 算法校验卡号
private boolean isValidCardNumber(String digits) {
    int sum = 0;
    boolean alternate = false;
    for (int i = digits.length() - 1; i >= 0; i--) {
        int n = digits.charAt(i) - '0';
        if (alternate) {
            n *= 2;
            if (n > 9) {
                n -= 9;
            }
        }
        sum += n;
        alternate = !alternate;
    }
    return sum % 10 == 0;
}

校验失败时可以在输入框旁边给出红色提示,或者直接把边框染成红色:cardField.setBorder(BorderFactory.createLineBorder(Color.RED)),校验通过后再恢复默认边框。需要注意的是,失焦校验和掩码显示应该同时进行:即使卡号校验不通过,掩码逻辑也不应中断,否则用户每次失焦都会看到完整卡号,保护效果就失效了。

最后补充一点架构层面的建议。真实卡号的持有者最好不要是输入框组件本身,而是引入一个简单的模型类(例如 CardModel),输入框只作为视图监听模型变化。这样后续如果要把卡号提交给后端、记录日志或做加密存储,都通过模型操作,界面层不接触明文逻辑,代码职责更清晰,也更容易写单元测试。

总结一下,实现聚焦显示全部、失焦只显示后四位的信用卡输入框,核心是 FocusListener 配合独立保存的真实卡号变量,再叠加 DocumentFilter 做输入过滤和四位分组,最后用 Luhn 校验和光标管理提升体验。把这些组件组合起来,就能得到一个安全、规范且好用的 Swing 卡号录入控件。

Java Swing JFormattedTextField FocusListener修改时间:2026-08-31 21:39:11

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。