小编典典

Java正则表达式中捕获组的行为混乱

java

在这个答案中,我建议使用

s.replaceFirst("\\.0*$|(\\.\\d*?)0+$", "$1");

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

assertEquals("23", removeTrailingZeros("23.00"));

通过。确切的行为是否未定义?


阅读 217

收藏
2020-11-23

共1个答案

小编典典

当替换字符串中指定了一个不捕获任何内容的捕获组()时,参考实现中的Matcher类的文档未指定appendReplacement方法的行为null。尽管group方法的行为很明确,但方法中没有任何提及appendReplacement

以下是上述情况在实施上的3种不同表现:

  • 对于上述情况,参考实现不添加任何内容(或者可以说添加空字符串)。
  • GNU Classpath和Android的实现null针对上述情况进行了补充。

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

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

对于参考实现(Sun / Oracle JDK和OpenJDK),其代码appendReplacement似乎未从Java
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 {

参考

2)GNU类路径

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

Matcher.appendReplacementRE.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;
  }

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;

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));
              }
          }

并在捕获组未捕获时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;
    ...
  }

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

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

在Android中,Matcher.appendReplacement调用private方法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());
    }
}

由于Matcher.group(int)返回null用于捕获未能捕获组,Matcher.appendReplacement追加null当捕获组中的替换字符串被参考。

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

2020-11-23