You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 21与Java 17正则匹配行为不一致问题求助

Java 21 与 Java 17 正则表达式匹配行为差异的修复方案

问题复现

将Java 17项目迁移到Java 21后,正则匹配结果出现不一致:部分内容在Java 21中能匹配但Java 17中不能,反之亦然。可复现代码如下:

public static void main(String[] args) {
        //english
        test(
                "...their sample variance, and σ2N their population variance.",
                "(?<![A-Z\\$€£¥฿฿=]-?[0-9\\.]{0,5})((\\b|\\-)[0-9]{1,5}[0-9,.]{0,5}(€|¥|฿|฿|°C|°F|°De?|°R[éeøa]?|(Z|E|P|T|G|M|k|h|da|d|c|m|µ|n|f|z|y)[ΩΩm]|[ΩΩ]|(Z|E|P|T|G|M|k|h|da|d|c|m|µ|n|p|f|a|z|y)?N|[kKMGTPEZY]i?B|[kmµnp]g|[Mk]t|kWh|GWa|MWd|MWh)(?!\\w))",
                true,
                null);
        //french
        test(
                "Il a été mis au banc de la société.",
                "\\bau (banc) (?:des nations|de la (?:société|ville|communauté|France)|de l['´‘’′](?:Europe|empire|église|islam))\\b",
                false,
                "au banc de la société");
    }

    private static void test(String text, String regex, boolean caseSensitive, String expected) {
        int flags = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
        Pattern pattern = Pattern.compile(regex, flags);
        Matcher matcher = pattern.matcher(text);
        int start = 0;
        String match = null;
        while (matcher.find(start)) {
            match = text.substring(matcher.start(), matcher.end());
            start = matcher.end();
        }
        System.out.println("Expected: " + expected);
        System.out.println("Got: " + match);
    }

Java 17 运行输出

Expected: null
Got: null
Expected: au banc de la société
Got: au banc de la société

Java 21 运行输出

Expected: null
Got: 2N
Expected: au banc de la société
Got: null

差异原因

Java 21对正则引擎做了两处关键更新,导致匹配行为变化:

  • 单词边界\b的Unicode规则调整:Java 21严格遵循Unicode标准判定\b,对于带变音符号的字符(如法语société中的é),\b无法匹配é与后续.的边界——而Java 17的判定逻辑更宽松,允许该位置被识别为单词边界。
  • 前置否定环视的字符类匹配逻辑优化:第一个测试用例中的希腊字母σ未被包含在前置环视的排除字符类中,Java 21对字符类的匹配范围判定更严格,导致σ后的2N避开了环视限制,被错误匹配。

修复方案

针对第一个测试用例(英文文本)

扩展前置环视的排除字符类,加入希腊字母σ,避免σ后的数字被误匹配:

(?<![A-Zσ\\$€£¥฿฿=]-?[0-9\\.]{0,5})((\\b|\\-)[0-9]{1,5}[0-9,.]{0,5}(€|¥|฿|฿|°C|°F|°De?|°R[éeøa]?|(Z|E|P|T|G|M|k|h|da|d|c|m|µ|n|f|z|y)[ΩΩm]|[ΩΩ]|(Z|E|P|T|G|M|k|h|da|d|c|m|µ|n|p|f|a|z|y)?N|[kKMGTPEZY]i?B|[kmµnp]g|[Mk]t|kWh|GWa|MWd|MWh)(?!\\w))

针对第二个测试用例(法语文本)

移除末尾的\b,或使用基于Unicode属性的边界判定替代\b,确保跨版本兼容:
方案1(移除\b):

\\bau (banc) (?:des nations|de la (?:société|ville|communauté|France)|de l['´‘’′](?:Europe|empire|église|islam))

方案2(使用明确的边界判定):

(?<=\\s|^)au (banc) (?:des nations|de la (?:société|ville|communauté|France)|de l['´‘’′](?:Europe|empire|église|islam))(?=\\s|$)

通用迁移建议

  • 正则编译时添加Pattern.UNICODE_CHARACTER_CLASS标志,强制使用Unicode标准的字符类规则,减少版本间差异。
  • 避免依赖\b的模糊判定,改用基于Unicode属性的表达式(如\p{L}匹配字母、\p{P}匹配标点)来定义边界。
  • 测试时覆盖包含非ASCII字符(变音符号、特殊符号等)的场景,确保正则在不同Java版本下行为一致。

内容的提问来源于stack exchange,提问作者SteVio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 03:43:16