10

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

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

由于有几个正则表达式来描述评论,我想找到一种方法来区分它们。

如何做呢?该类Pattern不实现该equals方法。

4

9 回答 9

8

您可以Pattern通过比较调用的结果来比较对象,pattern()但这toString并不能满足您的要求(如果我正确理解您的问题)。具体来说,这会比较传递给Pattern.compile(...)工厂方法的字符串。但是,这没有考虑单独传递给模式字符串的标志。

没有简单的方法来测试两个不同的正则表达式是否等价。例如".+""..*"表示等效的正则表达式,但没有直接的方法可以使用PatternAPI 来确定这一点。


我不知道这个问题在理论上是否可以解决......在一般情况下。 @Akim评论:

正则表达式等价没有有限的公理化,所以简短的回答是“这不是通过正则表达式本身的树转换来实现的”。但是,可以比较两个自动机的语言(测试它们的相等性),因此可以计算两个正则表达式是否等价。请注意,我指的是“真正的”正则表达式,没有诸如对捕获组的反向引用之类的扩展,它们逃脱了理性语言的领域,即自动机的领域。


我还想评论接受的答案。作者提供了一些他声称的代码表明Pattern 的equals方法是从Object. 事实上,他看到的输出与那个是一致的……但它并没有显示出来。

知道是否是这种情况的正确方法是查看 javadoc ...该equals方法在继承方法列表中列出。那是确定的。

那么为什么这个例子没有显示作者所说的内容呢?

  1. 两种方法的行为方式可能相同,但实现方式不同。如果我们把这个Pattern类当作一个黑盒子,那么我们就不能证明这没有发生。(或者至少......不是不使用反射。)

  2. 作者只在一个平台上运行过这个。其他平台的行为可能会有所不同。

Pattern关于第二点,我记得在(Java 1.4 中)的早期实现中,这些Pattern.compile(...)方法保留了最近编译的模式对象1的缓存。如果你编译了两次特定的模式字符串,第二次你可能会得到与第一次返回的对象相同的对象。这将导致测试代码输出:

  true
  true
  true
  true

但这说明了什么?它是否显示Pattern覆盖Object.equals?不!

这里的教训是,您应该主要通过查看 javadocs来了解 Java 库方法的行为方式:

  • 如果您编写“黑匣子”测试,您可能会得出错误的结论……或者至少,得出的结论可能并非适用于所有平台。

  • 如果您将结论建立在“阅读代码”的基础上,那么您就有可能得出对其他平台无效的结论。


1 - 即使我的记忆不正确,这样的实现也会与Pattern.compile(...)方法的 javadocs 一致。他们没有说每次compile调用都会返回一个新Pattern对象。

于 2012-04-07T15:25:47.963 回答
5

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


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

于 2012-04-07T14:00:26.283 回答
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!
    }
于 2014-04-17T17:42:38.443 回答
2

Pattern不会,但String会。为什么不只比较Pattern编译 s 的正则表达式呢?

于 2012-04-07T13:55:18.743 回答
2

我知道自动机可以解决你的问题。但这可能很复杂。粗略地说,您应该至少进行比较pattern.pattern()pattern.flags()尽管仅仅决定两个正则表达式是否等效是不够的。

于 2016-06-07T03:11:18.527 回答
0

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

Pattern p1 = getPattern1();
Pattern p2 = getPattern2();
if (p1.pattern().equals(p2.pattern())){
    // your code here
}
于 2012-04-07T14:03:59.553 回答
0

我想我明白了这个问题,因为我搜索了比较Patterns 的方法,所以我最终来到了这里(可能为时已晚两年,好吧,对不起......)。

我正在编写测试,我需要知道我的方法是否返回预期的模式。虽然通过toString()or的文本pattern()可能相同,但标志可能不同,并且使用该模式时的结果将是意想不到的。

不久前,我编写了自己的通用实现toString()。它收集包括这些字段在内的所有字段private,并构造一个可用于记录和显然用于测试的字符串。它显示了在编译两个相等的模式时字段rootmatchRoot是不同的。假设这两个与平等无关,并且由于有一个 field 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);
于 2014-06-12T17:01:44.800 回答
0

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

boolean isPatternEqualToPattern(final Pattern p1, final Pattern p2) {
    return p1.flags() == p2.flags() &&
        p1.pattern().equals(p2.pattern());
}
于 2015-04-09T22:27:23.857 回答
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

于 2016-06-07T09:12:02.750 回答