如何在Java中使用正则表达式?

如何在Java中使用正则表达式?

sjmyuan 57 2023-02-20

背景

最近需要使用正则表达式对字符串进行解析。例如给定一个字符串a=1,我们需要提取出=两边的字符串,然后分别赋给变量keyvalue。实现如下

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中的OptionTuple创建了函数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;
        }
    };
}