在java正则表达式中捕获组的行为混淆

maa*_*nus 7 java regex replace

在这个答案我推荐使用

s.replaceFirst("\\.0*$|(\\.\\d*?)0+$", "$1");
Run Code Online (Sandbox Code Playgroud)

但有两个人抱怨结果包含字符串"null",例如23.null.这可以通过$1(即group(1))存在来解释null,可以将其转换String.valueOf为字符串"null".但是,我总是得到空字符串.我的测试用例涵盖了它

assertEquals("23", removeTrailingZeros("23.00"));
Run Code Online (Sandbox Code Playgroud)

经过.确切的行为是否未定义?

nha*_*tdh 5

当在替换字符串中指定不捕获任何内容 ( )的捕获组时,参考实现中的Matcher类的文档未指定appendReplacement方法的行为null。虽然group方法的行为很清楚,但方法中没有提到任何内容appendReplacement

以下是上述案例的实施差异的 3 个表现:

  • 对于上述情况,参考实现没有附加任何内容(或者我们可以说附加一个空字符串)。
  • GNU Classpath 和 Android 的实现附加null在上述案例中。

为简洁起见,省略了一些代码,并用 表示...

1)Sun/Oracle JDK、OpenJDK(参考实现)

对于参考实现(Sun/Oracle JDK 和 OpenJDK),appendReplacementJava 6的代码似乎没有改变,并且当捕获组不捕获任何内容时,它不会附加任何内容:

        } else if (nextChar == '$') {
            // Skip past $
            cursor++;
            // The first number is always a group
            int refNum = (int)replacement.charAt(cursor) - '0';
            if ((refNum < 0)||(refNum > 9))
                throw new IllegalArgumentException(
                    "Illegal group reference");
            cursor++;

            // Capture the largest legal group string
            ...

            // Append group
            if (start(refNum) != -1 && end(refNum) != -1)
                result.append(text, start(refNum), end(refNum));
        } else {
Run Code Online (Sandbox Code Playgroud)

参考

2) GNU 类路径

GNU 类路径是 Java 类库的完整重新实现,appendReplacement在上述情况下具有不同的实现。在 Classpath 中,Classpath 中java.util.regexpackage 中的类只是gnu.java.util.regex.

Matcher.appendReplacement调用RE.getReplacement以处理匹配部分的替换:

  public Matcher appendReplacement (StringBuffer sb, String replacement)
    throws IllegalStateException
  {
    assertMatchOp();
    sb.append(input.subSequence(appendPosition,
                                match.getStartIndex()).toString());
    sb.append(RE.getReplacement(replacement, match,
        RE.REG_REPLACE_USE_BACKSLASHESCAPE));
    appendPosition = match.getEndIndex();
    return this;
  }
Run Code Online (Sandbox Code Playgroud)

RE.getReplacement调用REMatch.substituteInto获取捕获组的内容并直接附加其结果:

                  case '$':
                    int i1 = i + 1;
                    while (i1 < replace.length () &&
                           Character.isDigit (replace.charAt (i1)))
                      i1++;
                    sb.append (m.substituteInto (replace.substring (i, i1)));
                    i = i1 - 1;
                    break;
Run Code Online (Sandbox Code Playgroud)

REMatch.substituteIntoREMatch.toString(int)不检查捕获组是否捕获了任何内容,直接追加的结果:

        if ((input.charAt (pos) == '$')
            && (Character.isDigit (input.charAt (pos + 1))))
          {
            // Omitted code parses the group number into val
            ...

            if (val < start.length)
              {
                output.append (toString (val));
              }
          }
Run Code Online (Sandbox Code Playgroud)

并在捕获组未捕获时REMatch.toString(int)返回null(省略了无关代码)。

  public String toString (int sub)
  {
    if ((sub >= start.length) || sub < 0)
      throw new IndexOutOfBoundsException ("No group " + sub);
    if (start[sub] == -1)
      return null;
    ...
  }
Run Code Online (Sandbox Code Playgroud)

因此,在 GNU Classpath 的情况下,null当替换字符串中指定了无法捕获任何内容的捕获组时,将附加到字符串。

3) Android 开源项目——Java 核心库

在 Android 中,Matcher.appendReplacement调用私有方法appendEvaluated,该方法又直接将 的结果附加group(int)到替换字符串中。

public Matcher appendReplacement(StringBuffer buffer, String replacement) {
    buffer.append(input.substring(appendPos, start()));
    appendEvaluated(buffer, replacement);
    appendPos = end();
    return this;
}

private void appendEvaluated(StringBuffer buffer, String s) {
    boolean escape = false;
    boolean dollar = false;
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c == '\\' && !escape) {
            escape = true;
        } else if (c == '$' && !escape) {
            dollar = true;
        } else if (c >= '0' && c <= '9' && dollar) {
            buffer.append(group(c - '0'));
            dollar = false;
        } else {
            buffer.append(c);
            dollar = false;
            escape = false;
        }
    }
    // This seemingly stupid piece of code reproduces a JDK bug.
    if (escape) {
        throw new ArrayIndexOutOfBoundsException(s.length());
    }
}
Run Code Online (Sandbox Code Playgroud)

由于捕获失败的捕获组Matcher.group(int)返回null,因此在替换字符串中引用捕获组时Matcher.appendReplacement追加null

向您抱怨的那两个人很可能是在 Android 上运行他们的代码。