匹配全限定类名的正则表达式

Posted

技术标签:

【中文标题】匹配全限定类名的正则表达式【英文标题】:Regular expression matching fully qualified class names 【发布时间】:2011-07-09 11:40:37 【问题描述】:

在文本中匹配完全限定的 Java 类名的最佳方法是什么?

示例:java.lang.Reflectjava.util.ArrayListorg.hibernate.Hibernate

【问题讨论】:

这些出现在什么上下文中,java import 语句?如果只有 ; 要删除,那么 不要 使用正则表达式 忘记正则表达式;见javax.lang.model.SourceVersion.isName(CharSequence) 【参考方案1】:

Java 完全限定的类名(比如说“N”)具有结构

N.N.N.N

“N”部分必须是 Java 标识符。 Java 标识符不能以数字开头,但在初始字符之后可以使用字母和数字、下划线或美元符号的任意组合:

([a-zA-Z_$][a-zA-Z\d_$]*\.)*[a-zA-Z_$][a-zA-Z\d_$]*
------------------------    -----------------------
          N                           N

它们也不能是保留字(如importtruenull)。如果您只想检查合理性,以上就足够了。如果您还想检查有效性,还必须检查保留字列表。

Java 标识符可以包含任何 Unicode 字母,而不是“仅限拉丁语”。如果您也想检查这一点,请使用 Unicode 字符类:

([\pLetter_$][\pLetter\pNumber_$]*\.)*[\pLetter_$][\pLetter\pNumber_$]*

或者,简称

([\pL_$][\pL\pN_$]*\.)*[\pL_$][\pL\pN_$]*

Java Language Specification, (section 3.8) 包含有关有效标识符名称的所有详细信息。

另请参阅此问题的答案:Java Unicode variable names

【讨论】:

Java 标识符可以以任何货币符号开头,因此 $val、£val 和 ¥val 都是有效的。我认为这适用于类和变量。查看java apidownload.oracle.com/javase/1.5.0/docs/api/java/lang/… @Richard:好的,谢谢你的信息。然后应该使用\pCurrency_Symbol\pSc 而不是$。想想看,一个反复调用isJavaIdentifierPart()isJavaIdentifierStart() 的小型解析器会产生更简洁的代码。 我同意解析器是这样做的方法,这几乎就像 Java 语言设计人员在编写 Character API 时考虑到了这一点;)但是问题是关于正则表达式,所以我认为你已经得到了正确的答案。来自我的 +1。 其实这些方法已经用特殊字符类来表示了。我们只需要匹配一个 Java 标识符就是"(\\pjavaJavaIdentifierStart\\pjavaJavaIdentifierPart*\\.)+\\pjavaJavaIdentifierStart\\pjavaJavaIdentifierPart*"。优雅,你的名字是Java! RegexBuddy 可以处理这个问题:([\pL_\pSc][\pL\pN_\pSc]*\.)+【参考方案2】:

这是一个完整的测试类,基于@alan-moore 的优秀评论

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.regex.Pattern;

import org.junit.Test;

public class ValidateJavaIdentifier 

    private static final String ID_PATTERN = "\\pjavaJavaIdentifierStart\\pjavaJavaIdentifierPart*";
    private static final Pattern FQCN = Pattern.compile(ID_PATTERN + "(\\." + ID_PATTERN + ")*");

    public static boolean validateJavaIdentifier(String identifier) 
        return FQCN.matcher(identifier).matches();
    


    @Test
    public void testJavaIdentifier() throws Exception 
        assertTrue(validateJavaIdentifier("C"));
        assertTrue(validateJavaIdentifier("Cc"));
        assertTrue(validateJavaIdentifier("b.C"));
        assertTrue(validateJavaIdentifier("b.Cc"));
        assertTrue(validateJavaIdentifier("aAa.b.Cc"));
        assertTrue(validateJavaIdentifier("a.b.Cc"));

        // after the initial character identifiers may use any combination of
        // letters and digits, underscores or dollar signs
        assertTrue(validateJavaIdentifier("a.b.C_c"));
        assertTrue(validateJavaIdentifier("a.b.C$c"));
        assertTrue(validateJavaIdentifier("a.b.C9"));

        assertFalse("cannot start with a dot", validateJavaIdentifier(".C"));
        assertFalse("cannot have two dots following each other",
                validateJavaIdentifier("b..C"));
        assertFalse("cannot start with a number ",
                validateJavaIdentifier("b.9C"));
    

【讨论】:

VALID_JAVA_IDENTIFIER 是错误的名称选择,因为该模式代表 FQCN。我建议提取String ID_PATTERN = "\\pjavaJavaIdentifierStart\\pjavaJavaIdentifierPart*" 使其更明显和可读。 @TWiStErRob 不确定您所说的 VALID_JAVA_IDENTIFIER 代表 FQCN 是什么意思?另外,不确定ID_PATTERN 是否更具可读性...感谢您的解释。 有效的 java 标识符可以是方法名、局部变量、类名、子包名等。但是,您的“VALID_JAVA_IDENTIFIER”模式匹配由多个标识符组成的完全限定类名 (FQCN) (每个子包+类名一个)。 FQCN 不是一个有效的 java 标识符,因为它包含点。对于ID_PATTERN,请参阅我对 Jörgen 回答的编辑;更容易查看重复的内容和时间,您也不必滚动或换行。【参考方案3】:

Renaud 提供的模式有效,但他的原始答案总是会在最后回溯。

要优化它,您基本上可以将前半部分与后半部分交换。请注意您还需要更改的点匹配。

以下是我的版本,与原始版本相比,它的运行速度大约是原来的两倍:

String ID_PATTERN = "\\pjavaJavaIdentifierStart\\pjavaJavaIdentifierPart*";
Pattern FQCN = Pattern.compile(ID_PATTERN + "(\\." + ID_PATTERN + ")*");

我不会写cmets,所以我决定写一个答案。

【讨论】:

【参考方案4】:

我(自己)得出了一个类似的答案(如 Tomalak 的答案),类似于 M.M.M.N:

([a-z][a-z_0-9]*\.)*[A-Z_]($[A-Z_]|[\w_])*

在哪里,

M = ([a-z][a-z_0-9]*\.)*
N = [A-Z_]($[A-Z_]|[\w_])*

但是,这个正则表达式(与 Tomalak 的答案不同)做了更多假设:

    包名(M部分)只能小写,M的第一个字符总是小写字母,其余可以混合下划线、小写字母和数字。

    李>

    类名(N 部分)总是以大写字母或下划线开头,其余可以混合下划线、字母和数字。内部类总是以美元符号 ($) 开头,并且必须遵守前面描述的类名规则。

注意:模式\w是字母和数字的XSD模式(不包括下划线符号(_))

希望对您有所帮助。

【讨论】:

【参考方案5】:

以下类验证提供的包名是否有效:

import java.util.HashSet;

public class ValidationUtils 

    // All Java reserved words that must not be used in a valid package name.
    private static final HashSet reserved;

    static 
        reserved = new HashSet();
        reserved.add("abstract");reserved.add("assert");reserved.add("boolean");
        reserved.add("break");reserved.add("byte");reserved.add("case");
        reserved.add("catch");reserved.add("char");reserved.add("class");
        reserved.add("const");reserved.add("continue");reserved.add("default");
        reserved.add("do");reserved.add("double");reserved.add("else");
        reserved.add("enum");reserved.add("extends");reserved.add("false");
        reserved.add("final");reserved.add("finally");reserved.add("float");
        reserved.add("for");reserved.add("if");reserved.add("goto");
        reserved.add("implements");reserved.add("import");reserved.add("instanceof");
        reserved.add("int");reserved.add("interface");reserved.add("long");
        reserved.add("native");reserved.add("new");reserved.add("null");
        reserved.add("package");reserved.add("private");reserved.add("protected");
        reserved.add("public");reserved.add("return");reserved.add("short");
        reserved.add("static");reserved.add("strictfp");reserved.add("super");
        reserved.add("switch");reserved.add("synchronized");reserved.add("this");
        reserved.add("throw");reserved.add("throws");reserved.add("transient");
        reserved.add("true");reserved.add("try");reserved.add("void");
        reserved.add("volatile");reserved.add("while");
    

    /**
     * Checks if the string that is provided is a valid Java package name (contains only
     * [a-z,A-Z,_,$], every element is separated by a single '.' , an element can't be one of Java's
     * reserved words.
     *
     * @param name The package name that needs to be validated.
     * @return <b>true</b> if the package name is valid, <b>false</b> if its not valid.
     */
    public static final boolean isValidPackageName(String name) 
        String[] parts=name.split("\\.",-1);
        for (String part:parts)
            System.out.println(part);
            if (reserved.contains(part)) return false;
            if (!validPart(part)) return false;
        
        return true;
    

    /**
     * Checks that a part (a word between dots) is a valid part to be used in a Java package name.
     * @param part The part between dots (e.g. *PART*.*PART*.*PART*.*PART*).
     * @return <b>true</b> if the part is valid, <b>false</b> if its not valid.
     */
    private static boolean validPart(String part)
        if (part==null || part.length()<1)
            // Package part is null or empty !
            return false;
        
        if (Character.isJavaIdentifierStart(part.charAt(0)))
            for (int i = 0; i < part.length(); i++)
                char c = part.charAt(i);
                if (!Character.isJavaIdentifierPart(c))
                    // Package part contains invalid JavaIdentifier !
                    return false;
                
            
        else
            // Package part does not begin with a valid JavaIdentifier !
            return false;
        

        return true;
    

【讨论】:

【参考方案6】:

工作正则表达式的较短版本:

\pAlnum[\pAlnum._]+\pAlnum

【讨论】:

【参考方案7】:

对于像 com.mycompany.core.functions.CustomFunction 这样的字符串,我使用的是((?:(?:\w+)?\.[a-z_A-Z]\w+)+)

【讨论】:

【参考方案8】:

以下表达式对我来说非常有效。

^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+$

【讨论】:

区分大小写很重要【参考方案9】:

我会说类似([\w]+\.)*[\w]+

但也许我可以更具体地知道你想用它做什么;)

【讨论】:

你不需要[],这应该足够了(\\w+\\.?)+ 我认为 [] 让事情变得更清晰,正则表达式已经够乱了 ;) 我让最后一点在外面清楚地将包与类名分开。 我想看看给定的输入是否是一个好的java类名(完全限定包),使用hibernate验证器(通过@Pattern的注释样式)。

以上是关于匹配全限定类名的正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式

jmeter之正则表达式

JS的正则表达式限定开始和结尾等测试

正则表达式的限定符或运算符字符类元字符贪婪/懒惰匹配

.Net 正则表达式处理中“$”限定符的潜在不当行为 [重复]

学习笔记:正则表达式