Java比较两个Pattern对象

11

有没有一种简单的方法来比较两个Pattern对象?

我有一个Pattern,使用正则表达式"//"编译来检查代码中的注释。

由于有几个正则表达式可以描述注释,我想找到一种区分它们的方法。

该怎么做?Pattern类没有实现equals方法。

9个回答

8
您可以通过比较调用 pattern()toString 的结果来比较 Pattern 对象,但这并不能达到您想要的效果(如果我正确理解了你的问题)。具体来说,这比较了传递给 Pattern.compile(...) 工厂方法的字符串。然而,这不考虑单独传递给模式字符串的标志。
没有简单的方法来测试两个不同的正则表达式是否等价。例如,".+""..*" 表示等价的正则表达式,但是使用 Pattern API 没有直接的方法来确定这一点。
我不知道问题在一般情况下是否可以@Akim评论:

没有正则表达式等价的有限公理化形式,因此简短的答案是“这不可由正则表达式树转换实现”。但是可以比较两个自动机的语言(测试它们的相等性),所以可以计算出两个正则表达式是否等价。请注意,我指的是“真正”的正则表达式,没有扩展,如回溯引用来捕获组,这些超出了有限状态自动机的范围,即自动机的语言。


我还想评论一下被接受的答案。作者提供了一些代码,声称 equals 方法是从 Object 继承的。事实上,他看到的输出与此一致......但这并没有展示出它。
正确的方法是查看 javadoc......在继承方法列表中列出了 equals 方法。这是最权威的。
那么,为什么这个例子没有展示作者所说的内容呢?
  1. 两种方法可能表现出相同的方式,但是实现方式可能不同。如果我们将 Pattern 类视为黑盒,则无法证明这种情况不存在。(或者至少...不使用反射。)
  2. 作者只在一个平台上运行了此代码。其他平台的行为可能会有所不同。
关于第二点,我的回忆是,在 Java 1.4 中更早的 Pattern 实现中,Pattern.compile(...) 方法保留了最近编译的模式对象的缓存。1 如果您编译了特定的模式字符串两次,第二次您可能会得到与第一次返回的相同的对象。这将导致测试代码输出:
  true
  true
  true
  true

但这说明了什么呢?它是否表明Pattern覆盖了Object.equals?不是的!本课的教训是,您应该通过查看javadoc来确定Java库方法的主要行为:
- 如果您编写“黑盒”测试,则可能得出不正确的结论......或者至少得出的结论可能不适用于所有平台。 - 如果您基于“阅读代码”得出结论,则有可能得出对其他平台无效的结论。
即使我的回忆不正确,这样的实现也与Pattern.compile(...)方法的javadoc一致。它们没有说每个compile调用都返回一个新的Pattern对象。

正则表达式对象从未被自动缓存。API文档警告Pattern.matches()String#matches()不允许重复使用Pattern对象,因此不应在循环中重复调用。(Scanner类确实缓存其使用的所有模式,但这是在内部处理的。) - Alan Moore
我甚至不知道这个问题在理论上是否可解...在一般情况下。正则表达式的等价性没有有限的公理化,因此简短的答案是“无法通过正则表达式本身的树转换来完成”。然而,可以比较两个自动机的语言(测试它们的相等性),因此可以计算出两个正则表达式是否等价。请注意,我指的是“真正的”正则表达式,没有扩展,例如回溯到捕获组,这些扩展超出了有理语言的范畴,即自动机的范畴。 - akim

5
也许我没有完全理解问题。但是正如您在以下示例中所看到的,对于每个Java对象都有一个默认的 java.lang.Object.equals(Object)方法。该方法比较对象的引用,即使用 == 运算符。

package test;
import java.util.regex.Pattern; public class Main {
private static final Pattern P1 = Pattern.compile("//.*"); private static final Pattern P2 = Pattern.compile("//.*");
public static void main(String[] args) { System.out.println(P1.equals(P1)); System.out.println(P1.equals(P2)); System.out.println(P1.pattern().equals(P1.pattern())); System.out.println(P1.pattern().equals(P2.pattern())); } }

输出:


true
false
true
true


3
我知道自动机可能能解决你的问题,但这可能会很复杂。大致上,你至少应该比较 pattern.pattern()pattern.flags(),尽管这还不足以确定两个正则表达式是否等效。

3

由于某种神秘的原因,Pattern对象没有实现equals()方法。例如,下面这个简单的单元测试会失败:

    @Test
    public void testPatternEquals() {
        Pattern p1 = Pattern.compile("test");
        Pattern p2 = Pattern.compile("test");
        assertEquals(p1, p2); // fails!
    }

最常见的解决方法似乎是比较Pattern对象的字符串表示形式(返回用于创建Pattern的字符串):

    @Test
    public void testPatternEquals() {
        Pattern p1 = Pattern.compile("test");
        Pattern p2 = Pattern.compile("test");
        assertEquals(p1.toString(), p2.toString()); // succeeds!
    }

虽然在某些情况下这可能有效,但它并不适用于一般情况。这种方法至少会忽略比较用于编译Pattern的标志。 - Chriki

2

Pattern不行但String可以。为什么不直接比较编译的正则表达式呢?


虽然在某些情况下这可能有效,但在一般情况下不起作用。这种方法至少会省略比较用于编译“Pattern”的标志。 - Chriki

0

要确定两个Pattern对象是否相等,最简单的方法是比较实际的字符串模式和用于创建该模式的标志:

boolean isPatternEqualToPattern(final Pattern p1, final Pattern p2) {
    return p1.flags() == p2.flags() &&
        p1.pattern().equals(p2.pattern());
}

0

您可以比较已生成模式的字符串表示:

Pattern p1 = getPattern1();
Pattern p2 = getPattern2();
if (p1.pattern().equals(p2.pattern())){
    // your code here
}

0

虽然其他答案可能解决了问题,但我认为它们并不是真正的问题答案。

如果你真的想比较两个模式,你实际上想要比较两个正则语言。

为了做到这一点,cs stackexchange已经发布了一个解决方案: https://cs.stackexchange.com/questions/12876/equivalence-of-regular-expressions

检查正则语言等价性的快速方法是Hopcroft和Karp算法(HK)。

这里是该算法的Java实现: http://algs4.cs.princeton.edu/65reductions/HopcroftKarp.java.html


0

我认为我理解了问题的意思,因为我搜索了比较Pattern的方法,所以我来到了这里(可能晚了两年,抱歉...)。

我正在编写测试,需要知道我的一个方法是否返回了预期的模式。虽然通过toString()pattern()返回的文本可能相同,但标志可以不同,使用模式时的结果也可能出乎意料。

一段时间以前,我编写了自己的toString()的通用实现。它收集所有字段,包括private字段,并构造一个可用于日志记录和测试的字符串。它显示当编译两个相等的模式时,字段rootmatchRoot是不同的。假设这两个字段对于相等性并不那么重要,而且由于存在flag字段,我的解决方案相当好,如果不是完美的话。

/**
 * Don't call this method from a <code>toString()</code> method with
 * <code>useExistingToString</code> set to <code>true</code>!!!
 */
public static String toString(Object object, boolean useExistingToString, String... ignoreFieldNames) {
  if (object == null) {
    return null;
  }

  Class<? extends Object> clazz = object.getClass();
  if (useExistingToString) {
    try {
      // avoid the default implementation Object.toString()
      Method methodToString = clazz.getMethod("toString");
      if (!methodToString.getDeclaringClass().isAssignableFrom(Object.class)) {
        return object.toString();
      }
    } catch (Exception e) {
    }
  }

  List<String> ignoreFieldNameList = Arrays.asList(ignoreFieldNames);
  Map<String, Object> fields = new HashMap<String, Object>();
  while (clazz != null) {
    for (Field field : clazz.getDeclaredFields()) {
      String fieldName = field.getName();
      if (ignoreFieldNameList.contains(fieldName) || fields.containsKey(fieldName)) {
        continue;
      }

      boolean accessible = field.isAccessible();
      if (!accessible) {
        field.setAccessible(true);
      }
      try {
        Object fieldValue = field.get(object);
        if (fieldValue instanceof String) {
          fieldValue = stringifyValue(fieldValue);
        }
        fields.put(fieldName, fieldValue);
      } catch (Exception e) {
        fields.put(fieldName, "-inaccessible- " + e.getMessage());
      }
      if (!accessible) {
        field.setAccessible(false);
      }
    }
    // travel upwards in the class hierarchy
    clazz = clazz.getSuperclass();
  }

  return object.getClass().getName() + ": " + fields;
}

public static String stringifyValue(Object value) {
  if (value == null) {
    return "null";
  }
  return "'" + value.toString() + "'";
}

测试通过:

String toString1 = Utility.toString(Pattern.compile("test", Pattern.CASE_INSENSITIVE), false, "root", "matchRoot");
String toString2 = Utility.toString(Pattern.compile("test", Pattern.CASE_INSENSITIVE), false, "root", "matchRoot");
assertEquals(toString1, toString2);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接