【问题标题】:java comparing two Pattern objectsjava比较两个Pattern对象
【发布时间】:2012-04-20 18:04:41
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: java regex


    【解决方案1】:

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

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


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

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


    我还想对已接受的答案发表评论。作者提供了一些他声称​​显示 Pattern 的equals 方法继承自Object 的代码。事实上,他看到的输出是 consistent 与那个......但它没有显示它。

    了解是否是这种情况的正确方法是查看 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 对象。

    【讨论】:

    • 模式对象从未被自动缓存。作为证据,API 文档警告 Pattern.matches()String#matches() 不允许重复使用 Pattern 对象,因此不应将其用于重复调用,例如在循环中。 (Scanner 类确实缓存它使用的所有模式,但这是在内部处理的。)
    • “我什至不知道这个问题在理论上是否可以解决......在一般情况下。”正则表达式等价没有有限的公理化,所以简短的回答是“这不是通过正则表达式本身的树转换来实现的”。但是,可以比较两个自动机的语言(测试它们的相等性),因此可以计算两个正则表达式是否等价。请注意,我指的是“真正的”正则表达式,没有任何扩展,例如对捕获组的反向引用,它们逃脱了理性语言的领域,即自动机的领域。
    【解决方案2】:

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

    如果你真的想比较两种模式,你基本上想比较两种常规语言

    为此,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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

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

          我正在编写测试,我需要知道我的方法是否返回预期的模式。虽然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);
          

          【讨论】:

            【解决方案6】:

            由于神秘的原因,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 的标志。
            【解决方案7】:

            您可以比较创建模式的字符串表示形式:

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

            【讨论】:

              【解决方案8】:

              也许我对这个问题没有完全理解。但正如您在以下示例中所见,每个 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

              【讨论】:

                【解决方案9】:

                Pattern 不会,但String 会。为什么不直接比较编译 Patterns 的正则表达式?

                【讨论】:

                • 虽然这在某些情况下可能有效,但在一般情况下不会有效。这种方法至少会省略比较用于编译 Pattern 的标志。
                猜你喜欢
                • 2012-12-12
                • 2013-04-18
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-04-10
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多