有没有一种简单的方法来比较两个Pattern
对象?
我有一个Pattern
,使用正则表达式"//"
编译来检查代码中的注释。
由于有几个正则表达式可以描述注释,我想找到一种区分它们的方法。
该怎么做?Pattern
类没有实现equals
方法。
pattern()
或 toString
的结果来比较 Pattern
对象,但这并不能达到您想要的效果(如果我正确理解了你的问题)。具体来说,这比较了传递给 Pattern.compile(...)
工厂方法的字符串。然而,这不考虑单独传递给模式字符串的标志。".+"
和 "..*"
表示等价的正则表达式,但是使用 Pattern
API 没有直接的方法来确定这一点。
没有正则表达式等价的有限公理化形式,因此简短的答案是“这不可由正则表达式树转换实现”。但是可以比较两个自动机的语言(测试它们的相等性),所以可以计算出两个正则表达式是否等价。请注意,我指的是“真正”的正则表达式,没有扩展,如回溯引用来捕获组,这些超出了有限状态自动机的范围,即自动机的语言。
equals
方法是从 Object
继承的。事实上,他看到的输出与此一致......但这并没有展示出它。equals
方法。这是最权威的。Pattern
类视为黑盒,则无法证明这种情况不存在。(或者至少...不使用反射。)Pattern
实现中,Pattern.compile(...)
方法保留了最近编译的模式对象的缓存。1 如果您编译了特定的模式字符串两次,第二次您可能会得到与第一次返回的相同的对象。这将导致测试代码输出: true
true
true
true
Pattern
覆盖了Object.equals
?不是的!本课的教训是,您应该通过查看javadoc来确定Java库方法的主要行为:Pattern.compile(...)
方法的javadoc一致。它们没有说每个compile
调用都返回一个新的Pattern
对象。 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
pattern.pattern()
和 pattern.flags()
,尽管这还不足以确定两个正则表达式是否等效。由于某种神秘的原因,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
的标志。 - ChrikiPattern
不行但String
可以。为什么不直接比较编译的正则表达式呢?
要确定两个Pattern
对象是否相等,最简单的方法是比较实际的字符串模式和用于创建该模式的标志:
boolean isPatternEqualToPattern(final Pattern p1, final Pattern p2) {
return p1.flags() == p2.flags() &&
p1.pattern().equals(p2.pattern());
}
您可以比较已生成模式的字符串表示:
Pattern p1 = getPattern1();
Pattern p2 = getPattern2();
if (p1.pattern().equals(p2.pattern())){
// your code here
}
虽然其他答案可能解决了问题,但我认为它们并不是真正的问题答案。
如果你真的想比较两个模式,你实际上想要比较两个正则语言。
为了做到这一点,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
我认为我理解了问题的意思,因为我搜索了比较Pattern
的方法,所以我来到了这里(可能晚了两年,抱歉...)。
我正在编写测试,需要知道我的一个方法是否返回了预期的模式。虽然通过toString()
或pattern()
返回的文本可能相同,但标志可以不同,使用模式时的结果也可能出乎意料。
一段时间以前,我编写了自己的toString()
的通用实现。它收集所有字段,包括private
字段,并构造一个可用于日志记录和测试的字符串。它显示当编译两个相等的模式时,字段root
和matchRoot
是不同的。假设这两个字段对于相等性并不那么重要,而且由于存在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);
Pattern.matches()
和String#matches()
不允许重复使用Pattern对象,因此不应在循环中重复调用。(Scanner类确实缓存其使用的所有模式,但这是在内部处理的。) - Alan Moore