背景
最近需要使用正则表达式对字符串进行解析。例如给定一个字符串a=1
,我们需要提取出=
两边的字符串,然后分别赋给变量key
和value
。实现如下
String regex = "^(.*)=(.*)$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher("a=1");
bool matched = matcher.matches();
String key="";
String value="";
if(matched&&matcher.groupCount() == 2){
key = matcher.group(1);
value = matcher.group(2);
}
为了减少代码重复,我们使用Vavr中的Option
和Tuple
创建了函数regex2
public Option<Tuple2<String, String>> regex2(String value, Pattern pattern) {
Matcher matcher = pattern.matcher(value);
if (matcher.matches() && matcher.groupCount() == 2) {
return Option.of(Tuple.of(matcher.group(1), matcher.group(2)));
}
return Option.none();
}
然后我们的代码可以简化为
String key="";
String value="";
Option<Tuple2<String, String>> keyAndValue = regex2("a=1", Pattern.compile("^(.*)=(.*)$"))
if(keyAndValue.isDefined()) {
key = keyAndValue.get()._1();
key = keyAndValue.get()._2();
}
痛点
在需要解析多个正则表达式时不易使用。例如我们需要判断一个字符串是否匹配下面的正则表达式,如果匹配则执行对应的操作
^(.*)=(.*)$
^(.*)+(.*)$
^(.*)-(.*)$
使用regex2
的实现如下
public void process(String value) {
Option<Tuple2<String, String>> keyAndValue = regex2(value, Pattern.compile("^(.*)=(.*)$"))
if(keyAndValue.isDefined()) {
//执行对应操作
return;
}
Option<Tuple2<String, String>> add = regex2(value, Pattern.compile("^(.*)+(.*)$"))
if(add.isDefined()) {
//执行对应操作
return;
}
Option<Tuple2<String, String>> minus = regex2(value, Pattern.compile("^(.*)-(.*)$"))
if(minus.isDefined()) {
//执行对应操作
return;
}
//执行默认操作
}
可以看到上述代码中有很多相似代码,增加新的正则表达式或对某个结果做特殊验证也很不方便。
解决方案
我们可以使用Vavr的Pattern Matching来克服上述痛点。
首先,我们需要自定义一个正则表达式的Pattern
public static Pattern2<String, String, String> $Regex2(java.util.regex.Pattern pattern,
io.vavr.API.Match.Pattern<String, ?> p1, io.vavr.API.Match.Pattern<String, ?> p2) {
return new io.vavr.API.Match.Pattern2<String, String, String>() {
private static final long serialVersionUID = 1L;
@Override
public Tuple2<String, String> apply(String obj) {
Matcher matcher = pattern.matcher(obj);
matcher.matches();
return Tuple.of(matcher.group(1), matcher.group(2));
}
@Override
public boolean isDefinedAt(String obj) {
Matcher matcher = pattern.matcher(obj);
if (matcher.matches() && matcher.groupCount() == 2) {
return p1.isDefinedAt(matcher.group(1)) && p2.isDefinedAt(matcher.group(2));
}
return false;
}
};
}
然后,我们可以用Pattern Matching重新实现process
函数
public void process(String value) {
return Match.of(
Case($Regex2(Pattern.compile("^(.*)=(.*)$"), $(), $()) (key, value) ->
//执行对应操作
),
Case($Regex2(Pattern.compile("^(.*)+(.*)$"), $(), $()) (left, right) ->
//执行对应操作
),
Case($Regex2(Pattern.compile("^(.*)-(.*)$"), $(), $()) (left, right) ->
//执行对应操作
),
Case($()) (unused) ->
//执行默认操作
)
);
}
如果我们要添加新的正则表达式,只需要添加一个Case
语句
public void process(String value) {
return Match.of(
Case($Regex2(Pattern.compile("^(.*)=(.*)$"), $(), $()) (key, value) ->
//执行对应操作
),
Case($Regex2(Pattern.compile("^(.*)+(.*)$"), $(), $()) (left, right) ->
//执行对应操作
),
Case($Regex2(Pattern.compile("^(.*)-(.*)$"), $(), $()) (left, right) ->
//执行对应操作
),
Case($Regex2(Pattern.compile("^(.*)%(.*)$"), $(), $()) (left, right) ->
//执行对应操作
),
Case($()) (unused) ->
//执行默认操作
)
);
}
如果我们需要对解析结果做额外检查,例如值不能为空,只需要添加一个lambda函数
public void process(String value) {
return Match.of(
Case($Regex2(Pattern.compile("^(.*)=(.*)$"), $(x -> !x.isEmpty()), $(x -> !x.isEmpty())) (key, value) ->
//执行对应操作
),
Case($Regex2(Pattern.compile("^(.*)+(.*)$"), $(x -> !x.isEmpty()), $(x -> !x.isEmpty())) (left, right) ->
//执行对应操作
),
Case($Regex2(Pattern.compile("^(.*)-(.*)$"), $(x -> !x.isEmpty()), $(x -> !x.isEmpty())) (left, right) ->
//执行对应操作
),
Case($Regex2(Pattern.compile("^(.*)%(.*)$"), $(x -> !x.isEmpty()), $(x -> !x.isEmpty())) (left, right) ->
//执行对应操作
),
Case($()) (unused) ->
//执行默认操作
)
);
}
如果我们只需要提取一个值,也可以为这个类型的正则表达式定义一个Pattern
public Pattern1<String, String> $Regex1(java.util.regex.Pattern pattern,
io.vavr.API.Match.Pattern<String, ?> p1) {
return new Pattern1<String, String>() {
private static final long serialVersionUID = 1L;
@Override
public String apply(String obj) {
Matcher matcher = pattern.matcher(obj);
matcher.matches();
return matcher.group(1);
}
@Override
public boolean isDefinedAt(String obj) {
Matcher matcher = pattern.matcher(obj);
if (matcher.matches() && matcher.groupCount() == 1) {
return p1.isDefinedAt(matcher.group(1));
}
return false;
}
};
}