我如何在switch语句中匹配正则表达式?

6
我正在尝试在一个函数中使用switch语句来匹配令牌,其中一个令牌需要能够识别代码中定义的任何字符串或任何数字。请问是否可以像case "[a-z]+":这样对案例进行正则表达式定义?
很明显,我现在的模式不可到达,除非我将STRINGNUMBER作为参数传递进去。
public Token analyzeToken(String token) {
      Token tokenType = null;     

      switch (token) {

         case "STRING":
            Pattern p = Pattern.compile("[a-z]+");
            Matcher m = p.matcher(token);
            if(m.matches()) {
               tokenType = Token.STRING;
               break;
            }
         case "NUMBER":
            Pattern p = Pattern.compile("[0-9]+");
            Matcher m = p.matcher(token);
            if(m.matches()) {
               tokenType = Token.NUMBER;
               break;

         case "(":
            tokenType = Token.LEFT_PAREN;
            break;
         case ")":
            tokenType = Token.RIGHT_PAREN;
            break;
         case ".":
            tokenType = Token.PERIOD;
            break;
         case ":":
            tokenType = Token.COLON;
            break;
         case ";":
            tokenType = Token.SEMICOLON;

         default:
            tokenType = TOKEN.UNKNOWN;
            break;

      }
   }

什么是问题? - Gavriel
在我的case语句中,我有“STRING”和“NUMBER”,但这不会成为传递给函数的标记。那么我应该用case定义什么? - hax0r_n_code
你刚刚做到了。看看你的“default”。要么你不理解switch的工作原理,要么你不知道自己真正想要实现什么。也许向我们展示一下可能的输入会更好。令牌可以是什么? - Gavriel
@Gavriel 我加入了更多的细节。同时请看一下我的正则表达式,我正在寻找只包含字母或只包含数字的特定字符串。 - hax0r_n_code
@ajb 谢谢你的信息。 - hax0r_n_code
显示剩余6条评论
4个回答

7

不要在 switch 语句中做这件事,应该在条件语句或更好的情况下,在循环中完成:

private interface TokenMatcher {
    Token match(String s);
}
static List<TokenMatcher> matchers = new ArrayList<>();
static {
    final Pattern strPattern = Pattern.compile("[a-z]+");
    final Pattern numPattern = Pattern.compile("[0-9]+");
    matchers.add( new TokenMatcher {
        public Token match(String s) {
            Matcher m = strPattern.matcher(s);
            return m.matches() ? Token.STRING : null;
        }
    });
    matchers.add( new TokenMatcher {
        public Token match(String s) {
            Matcher m = numPattern.matcher(s);
            return m.matches() ? Token.NUMBER : null;
        }
    });
}

现在你可以这样做:
static Token match(String s) {
    for (TokenMatcher m : matchers) {
        Token t = m.match(s);
        if (t != null) {
            return t;
        }
    }
    return TOKEN.UNKNOWN;
}
for循环已经取代了switch语句,而 matchers列表中的条目已经取代了switch中的单个 case 。添加新的令牌类型就像将新的模式及其相关实现添加到matchers列表一样简单。 编辑:您可以通过使用类来替换上面的接口使解决方案更加简洁,如下所示:
private static final class TokenMatcher {
    private final Pattern p;
    private final Token t;
    public TokenMatcher(String pString, Token t) {
        p = Pattern.compile(pString);
        this.t = t;
    }
    public Token match(String s) {
        Matcher m = p.matcher(s);
        return m.matches() ? t: null;
    }
}

现在您的matchers初始化可以像这样完成:
matchers.add(new TokenMatcher("[a-z]+", Token.STRING));
matchers.add(new TokenMatcher("[0-9]+", Token.NUMBER));

这正是我在寻找的!谢谢。 - hax0r_n_code
@dasblinkenlight,不错的解决方案,但是您认为使用Map会更好一些吗?其中键为String模式,值为Token。然后在匹配方法中,您只需要迭代这些模式即可。这样可以减少重复代码。 - Ken Bekov
@KenBekov 在这种情况下,Map并不会给你带来很大的优势,因为你从未通过模式查找。本质上,对于您的程序来说,它变成了一个(Pattern,Token)元组列表。另一方面,设置一个类而不是一个接口,将使您减少重复的代码。请参见编辑。 - Sergey Kalinichenko
@dasblinkenlight,我们的解决方案完全相同(这并不奇怪,因为我的解决方案基于你的)。只有一个区别:我将模式存储为“String”,而你将模式存储为“Pattern”类的实例。在我的代码中,只有一个“Pattern”实例,它将在“match”方法完成后被释放。 - Ken Bekov
@KenBekov 存储一个字符串会强制你在每次循环中编译模式,这正是我尽可能避免的事情。 - Sergey Kalinichenko
@dasblinkenlight,同意。如果模式更复杂,最好将其预先编译存储。无论如何,这通常是在内存和性能之间做出的选择。 - Ken Bekov

2

这个解决方案的灵感来自@dasblinkenlight的解决方案,我只是试图改进它。

public class TokenMatcher{

    private HashMap<String, Token> tokenMap = new HashMap<String, Token>();
    {
        tokenMap.put("[a-z]+", Token.STRING);
        tokenMap.put("[0-9]+", Token.NUMBER);
        tokenMap.put("\\(", Token.RIGHT_PARENT);
        tokenMap.put("\\)", Token.LEFT_PARENT);
        ...
    }


    public Token match(String s){
        for(String key : tokenMap.keySet()){
            Pattern pattern = Pattern.compile(key);
            Matcher matcher = pattern.matcher(s);
            if(matcher.matches()) return tokenMap.get(key);
        }
        return Token.UNKNOWN;
    }
}

改进:更容易添加新的令牌,减少重复代码,不需要额外的接口。

1

你需要2个参数:

public Token analyzeToken(String token, String string) {
      Token tokenType = null;     
      switch (token) {
         case "STRING":
            Pattern p = Pattern.compile("[a-z]+");
            Matcher m = p.matcher(string); // match the string, not the token!
            if(m.matches()) {
               tokenType = Token.STRING;
               break;
            }

更新:

public Token analyzeToken(String regex, String string) {
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(string); // match the string, not the token!
            if(m.matches()) {
               // ...
            }

1
也许这对你来说不是最佳解决方案,但通常你可以将正则表达式作为枚举传递给 switch 语句。只有当你不需要动态创建正则表达式时,这才是一个好的选择。
enum PatternCase {
    pattern1("regexp1"),
    pattern2("regexp2"),
    pattern3("regexp3"),
    pattern4("regexp4", Pattern.CASE_INSENSITIVE),
    ;
    final Pattern pattern;
    PatternCase(String regExp) { pattern = Pattern.compile(regExp); }
    PatternCase(String regExp, int flags) { pattern = Pattern.compile(regExp, flags); }
}

那么你就拥有了一个完全运作的开关,其正则表达式为其 case 语句。

        Matcher matcher = null;
        PatternCase matchedPatternCase = null;  // Or use some default value to avoid null checks.

        // Match only these patterns and match them in this effective order.
        for (PatternCase patternCase : new PatternCase[]{ PatternCase.pattern3, PatternCase.pattern1 }) {
            matcher = patternCase.pattern.matcher(text);
            if (matcher.find()) {
                matchedPatternCase = patternCase;
                break;
            }
        }

        if (matchedPatternCase != null) switch (matchedPatternCase) {
            case pattern1:
                System.out.println(matcher.group());
                break;
            case pattern3:
                do { System.out.println(matcher.group()); } while (matcher.find());
                break;
            default:
                break;
        }

如果你的 Token 值已经是枚举类型,那么直接在 Token 枚举中使用 Pattern 并遍历所有 Token 枚举值可能是合适的选择。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接