如何使用正则表达式匹配方法块?

6
举个例子。
 public static FieldsConfig getFieldsConfig(){
    if(xxx) {
      sssss;
    }
   return;
}

我写了一个正则表达式:"\\s*public\\s*static.*getFieldsConfig\\(.*\\)\\s*\\{"。它只能匹配到方法的第一行。但是如何匹配到方法最后一个"}"呢?请帮我解决这个问题,谢谢。
编辑:
方法内部的内容未指定。但是模式肯定是这样的。
  public static xxx theKnownMethodName(xxxx) {
    xxxxxxx
  }

2
你不能使用正则表达式解析Java。 - Andy Turner
请参见 https://dev59.com/VHRB5IYBdhLWcg3wtJGF - 这是关于括号的问题,但花括号也没有什么不同。 - Andy Turner
1
你不能用正则表达式真正解析除了常规语言以外的任何内容。但是你可以扫描它。 - user207421
1
@VictorChoy 人们告诉你的是:正则表达式并不适用于编程语言。你需要一个解析器。除非你能保证你的代码将处理的所有方法都具有相同的模式。换句话说:也许你想添加一些信息,说明你要“匹配”的源来自哪里;以及你想对它做什么。 - GhostCat
@Jägermeister 谢谢。我只是想知道是否可以用简单的正则表达式方式实现。如果不可能,我会自己读取文件进行解析。 - Victor Choy
显示剩余2条评论
7个回答

4

我决定向前迈进一步 ;)

这是一个正则表达式,它将以不同的捕获组给出函数的修饰符、类型、名称和主体:

((?:(?:public|private|protected|static|final|abstract|synchronized|volatile)\s+)*)
\s*(\w+)\s*(\w+)\(.*?\)\s*({(?:{[^{}]*}|.)*?})

它处理嵌套的括号 (@callOfCode 它可以使用正则表达式 ;) 和一组固定的修饰符。

它不能处理像注释中的括号之类的复杂内容,但对于最简单的情况可以使用。

问候

在这里查看Regex101示例

编辑:回答你的问题 ;),你感兴趣的是捕获组4。

编辑2:就像我说的 - 简单。但您可以使其更加复杂以处理更多复杂的方法。在这里更新处理一个更深层次的嵌套

((?:(?:public|private|protected|static|final|abstract|synchronized|volatile)\s+)*)
\s*(\w+)\s*(\w+)\(.*?\)\s*({(?:{[^{}]*(?:{[^{}]*}|.)*?[^{}]*}|.)*?})

您可以进入另一个级别... 然后再进入... 但是正如有人评论的那样 - 这不应该通过正则表达式完成。 不过,这可以处理简单的方法。


@Tim007 检查编辑2。你可以添加任意多的级别,但是正则表达式会变得越来越复杂。 - SamWhan
@ClasG 我已经在我的新答案中添加了一些细节,以解释正则表达式在现实场景中非常有限的原因。 - callOfCode

2

正则表达式并不是最好的工具,但如果你想使用正则表达式,并且你的代码格式良好,你可以尝试以下代码:

^(?<indent>\s*)(?<mod1>\w+)\s(?<mod2>\w+)?\s*(?<mod3>\w+)?\s*(?<return>\b\w+)\s(?<name>\w+)\((?<arg>.*?)\)\s*\{(?<body>.+?)^\k<indent>\}

演示

它有额外的命名组,你可以删除它们。它使用缩进级别来查找最后一个}


1
你需要启用DOTALL模式。这样点号就可以匹配换行符了。只需在正则表达式开头包含(?s)即可。
 String s = "   public static FieldsConfig getFieldsConfig(){\n"
             + "   if(xxx) {\n"
             + "              sssss;\n"
             + "   }\n"
             + "      return;\n"
             +"}";
 Matcher m = Pattern.compile("(?s)\\s*public\\s+static\\s+\\w+?\\sgetFieldsConfig\\(\\s*\\).*").matcher(s);
 m.find();
 System.out.println(m.group());

输出结果是您所需的所有方法体。如果没有(?s),它只匹配第一行。但是,您不能使用正则表达式解析Java代码。其他人已经说过了。这个正则表达式将匹配从方法签名开始到文件结尾的所有内容。如何仅匹配到达方法体末尾?方法可能包含许多{....}以及许多return;。正则表达式不是魔术棒。

方法 {xxx} 的内容未指定。 - Victor Choy
但是你如何指定方法体的结尾呢?你需要计算花括号,这在正则表达式中是不可能的。正则表达式对于嵌套结构是无用的。 - callOfCode
refer to my answer. ~~ - Victor Choy
我会在今天稍后参考它,首先我想要测试边缘情况,而现在我离我的笔记本电脑比较远。 - callOfCode

1

试试这个

((?<space>\h+)public\s+static\s+[^(]+\([^)]*?\)\s*\{.*?\k<space>\})|(public\s+static\s+[^(]+\([^)]*?\)\s*\{.*?\n\})
解释:
我们将通过关键字public开始捕获方法块,直到}结束,public}必须具有相同的\s字符,因此您的代码必须格式良好:)https://en.wikipedia.org/wiki/Indent_style \h:匹配空格但不包括换行符
(?<space>\h+): 获取public之前的所有空格,然后在space名称中分组
public\s+static\s: public static
[^(]: 任何字符但不是(
([^)]: 任何字符但不是)
\k<space>\}: }与结尾处的相同数量的空格,然后是}

演示

输入:

public static FieldsConfig getFieldsConfig(){
    if(xxx) {
      sssss;
    }
   return;
}

NO CAPTURE

public static FieldsConfig getFieldsConfig2(){
    if(xxx) {
      sssss;
    }
   return;
}

NO CAPTURE

    public static FieldsConfig getFieldsConfig3(){
        if(xxx) {
          sssss;
        }
       return;
    }

NO CAPTURE

        public static FieldsConfig getFieldsConfig4(){
            if(xxx) {
              sssss;
            }
           return;
        }

输出:

MATCH 1
3.  [0-91]  `public static FieldsConfig getFieldsConfig(){
    if(xxx) {
      sssss;
    }
   return;
}`

MATCH 2
3.  [105-197]   `public static FieldsConfig getFieldsConfig2(){
    if(xxx) {
      sssss;
    }
   return;
}`

MATCH 3
1.  [211-309]   `   public static FieldsConfig getFieldsConfig3(){
        if(xxx) {
          sssss;
        }
       return;
    }`

MATCH 4
1.  [324-428]   `       public static FieldsConfig getFieldsConfig4(){
            if(xxx) {
              sssss;
            }
           return;
        }`

我认为,如果您将最后一个闭合括号放在返回语句本身之后而不是在新行中,它将失败。 - Pragnani
@Pragnani Kinnera 它不会。 - Tim007
我认为你修改了你的表达式,现在它可以正常工作了。 - Pragnani
感谢您的想法和清晰的演示。我认为通过空格数量是一种有趣的方式。但多多少少,它并不可靠,希望能得到良好的格式。 - Victor Choy

1

Victor,你让我参考你的答案。所以我决定花时间写一份完整的审查并给出一些提示。我不是某种正则表达式专业人士,也不是很喜欢它。目前,我正在一个大量使用正则表达式的项目上工作,因此我已经看到并编写了足够多的内容来相当可靠地回答你的问题,并对正则表达式感到厌烦。 因此,让我们开始分析你的正则表达式:

String regex ="\\s*public\\s*static.*getFieldsConfig\\(.*?\\)\\s*\\{.*\\}(?=\\s*(public|private|protected|static))";

String regex2 = "\\s*public\\s*static.*getFieldsConfig\\(.*?\\)\\s*\\{.*\\}(?=(\\s*}\\s*$))";

regex = "(" + regex +")|("+ regex2 + "){1}?";

我看到你将内容分为三部分以便阅读。这是个好主意。我将从第一部分开始:

  • \\s\*public\\s\*static.*getFieldsConfig 你允许在publicstatic之间包括零个或多个空格。每次在必须用一些空格分隔的单词之间使用\\s+
  • (.\*?\\)\\s\*\\{.\*\\} 你允许在第一个括号中出现任何字符,直到匹配到)为止。现在我们来到了使你的正则表达式无法按照你想要的方式工作的部分。 \\{.*\\} 是一个重大错误。它将匹配直到文件中最后一个 }之前的所有内容,在到达任意一个publicprivateprotectedstatic之前。我将你的getFieldsConfig方法粘贴到Java文件中并进行了测试。仅使用你的正则表达式的第一部分("\\s*public\\s*static.*getFieldsConfig\\(.*?\\)\\s*\\{.*\\}(?=\\s*(public|private|protected|static))"),匹配到了从你的方法一直到文件中的最后一个方法的所有内容。

没有必要逐步分析其他部分,因为\\{.*\\}会破坏一切。在第二部分(regex2)中,您已将任何内容与文件中的最后一个}匹配。您尝试过打印正则表达式匹配的内容吗?试试:

package com.tryRegex;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TryRegex{

    public static void main(String[] args) throws IOException{
        File yourFile = new File("tryFile.java");
        Scanner scanner = new Scanner(yourFile, "UTF-8");
        String text = scanner.useDelimiter("\\A").next();  // `\\A` marks beginning of file. Since file has only one beginning, it will scan file from start to beginning.

        String regex ="\\s*public\\s*static.*getFieldsConfig\\(.*?\\)\\s*\\{.*\\}(?=\\s*(public|private|protected|static))";
        String regex2 = "\\s*public\\s*static.*getFieldsConfig\\(.*?\\)\\s*\\{.*\\}(?=(\\s*}\\s*$))";
        regex = "(?s)(" + regex +")|("+ regex2 + "){1}?";     // I've included (?s) since we reading from file newline chars are not excluded. Without (?s) it would match anything unless your method is written in a single line.

        Matcher m = Pattern.compile(regex).matcher(text);

        System.out.println(m.find() ? m.group() : "No Match found");
    }
}

一个简短而简单的代码片段,展示正则表达式的工作原理。如果需要,可以处理异常。只需将yourFile.java放入项目文件夹并运行即可。

现在我将展示正则表达式有多混乱:

String methodSignature = "(\\s*((public|private|protected|static|final|abstract|synchronized|volatile)\\s+)*[\\w<>\\[\\]\\.]+\\s+\\w+\\s*\\((\\s*[\\w<>\\[\\]\\.]*\\s+\\w+\\s*,?)*\\s*\\))";
String regex = "(?s)" + methodSignature + ".*?(?="+ methodSignature + ")";

基本上这个正则表达式匹配每个方法。但它也有缺陷。我将探讨它以及它的缺陷。
  • \\s*((public|private|protected|static|final|abstract|synchronized|volatile)\\s+)* 匹配指定修饰符(至少一个空格),可以出现任意次数,包括零,因为方法可能没有修饰符。(为了简单起见,我保留了允许的修饰符数量不受限制。在真正的解析器中,我也不会允许这样做,也不会使用正则表达式来完成这样的任务。)
  • [\\w<>\\[\\]\\.]+ 这是方法的返回类型。它可以包含单词字符,用于泛型类型的<>,用于数组的[]和用于嵌套类表示法的.
  • \\s+\\w+\\s*\\ 方法名。
  • \\((\\s*[\\w<>\\[\\]\\.]*\\s+\\w+\\s*,?)*\\s*\\)) 特别棘手的部分是方法参数。首先,您可能认为可以将此部分轻松替换为(。我也这样想。但是,我注意到它不仅匹配方法,还匹配匿名类,例如new Anonymous(someVariable){....}避免这种情况最简单和最有效的方法是指定方法参数结构。[\\w<>\\[\\]\\.]是参数类型可以由的可能符号。\\s+\\w+\\s*,? 参数类型后跟至少一个空格和参数名。如果方法包含多个参数,则参数名称后面可以跟随,

那么缺陷是什么呢?主要缺陷是在方法中定义类。方法可以包含类定义。考虑以下情况:

public void regexIsAGoodThing(){
  //some code
  new RegexIsNotSoGoodActually(){
    void dissapontingMethod(){
       //Efforts put in writing this regex was pointless because of this dissapointing method.
    }
  }
}

这很好地解释了为什么正则表达式不是处理此类任务的合适工具。由于方法可能是嵌套结构,因此无法可靠地从Java文件中解析方法。方法可能包含类定义,而这些类可能包含具有另一个类定义的方法,依此类推。正则表达式被无限递归所捕获并失败。
另一个正则表达式失败的情况是注释。在注释中,您可以输入任何内容。
void happyRegexing(){
     return void;
     // public void happyRegexingIsOver(){....}
}

我们不能忘记的另一件事是注解。如果下一个方法被注释了呢?那么这个正则表达式几乎可以匹配成功,除了它也会匹配注释。虽然可以避免这种情况,但正则表达式将变得更加庞大。

public void goodDay(){

}

@Zzzzz //This annotation can be carried out by making our regex even more larger
public void goodNight(){

}

另一个案例是代码块。如果两个方法之间包含静态或实例块怎么办?

public void iWillNotDoThisAnyMore(){

}

static{
    //some code
}

public void iWillNotParseCodeWithRegex(){
    //end of story
}
P.S 它还有另一个缺陷 - 它匹配 new SomeClass() 和下一个方法签名之间的所有内容。你可以解决这个问题,但是这只是一种变通方法,而不是优雅的代码。我还没有包括文件结尾的匹配。如果您感兴趣,也许明天我会添加编辑。现在要睡觉了,在欧洲已经接近早晨了。
正如您所看到的,正则表达式几乎是大多数任务的好工具。但是我们程序员讨厌“几乎”这个词。我们甚至没有将其纳入我们的词汇表中。难道不是吗?

太棒了!我非常欣赏你对于显式分析的能力。是的,使用正则表达式可能不是解析方法的严格途径。对我而言,我只是根据一些配置和Java类生成一个新的Java文件,该文件具有某些特定结构,例如所有静态和公共方法。总的来说,正则表达式比编程要简单,并在特定情况下起作用。这就是为什么我尝试选择它。不管怎样,非常感谢!~~ - Victor Choy

1
感谢大家。经过一些考虑,我在我的情况下找到了一种可靠的方法。现在分享给大家。
String regex ="\\s*public\s+static\s+[\w\.\<\>,\s]+\s+getFieldsConfig\\(.*?\\)\\s*\\{.*?\\}(?=\\s*(public|private|protected|static))";

String regex2 = "\\s*public\s+static\s+[\w\.\<\>,\s]+\s+getFieldsConfig\\(.*?\\)\\s*\\{.*?\\}(?=(\\s*}\\s*$))";

regex = "(" + regex +")|("+ regex2 + "){1}?";

Pattern pattern = Pattern.compile(regex, Pattern.DOTALL)

它可以很好地匹配我的方法体。

PS 是的,正则表达式可能不是非常严格解析方法的适当方式。一般来说,正则表达式比编程少花费努力,并在特定情况下工作得很好。调整它并确保它适合您的需求。


0

我不得不根据自己的需求修改这个答案。我想要捕获整个方法以及文件中每个方法的名称的捕获组。我只需要这两个捕获组。这需要在PCRE中使用单行(s)标志。全局(g)标志将需要在其他REGEX解析中捕获整个文件而不仅仅是一个匹配项。我嵌套了@SamWhan展示的括号捕获,以允许五层嵌套。这应该能够完成工作,但更多的嵌套违反了大多数推荐标准。这使得这个REGEX非常昂贵,所以请注意。

(?:public|private|protected|static|final|abstract|synchronized|volatile)\s*(?:(?:(?:\w*\s)?(\w+))|)\(.*?\)\s*(?:\{(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*}|.)*?[^{}]*}|.)*?[^{}]*}|.)*?[^{}]*}|.)*?[^{}]*}|.)*?[^{}]*}|.)*?})

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