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

46

如何在文本中匹配完整的Java类名?

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


这些出现在什么上下文中,Java的import语句?如果只需移除;,则不要使用正则表达式。 - Johan Sjöberg
1
忘记正则表达式吧,看看javax.lang.model.SourceVersion.isName(CharSequence) - Hollis Waite
9个回答

81

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字符类:

([\p{Letter}_$][\p{Letter}\p{Number}_$]*\.)*[\p{Letter}_$][\p{Letter}\p{Number}_$]*

简而言之

([\p{L}_$][\p{L}\p{N}_$]*\.)*[\p{L}_$][\p{L}\p{N}_$]*

有关有效标识符名称的详细信息,请参见Java语言规范(第3.8节)

还可以参见此问题的答案:Java Unicode变量名


2
Java 标识符可以以任何货币符号开头,因此 $val、£val 和 ¥val 都是有效的。我认为这也适用于类和变量。请参阅 Java API http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Character.html#isJavaIdentifierStart(char)。 - Richard Miskin
1
@Richard:好的,感谢提供信息。所以应该使用\p{Currency_Symbol}\p{Sc}代替$。想一想,一个调用isJavaIdentifierPart()isJavaIdentifierStart()的小解析器会使代码更简洁。 - Tomalak
34
实际上,这些方法已经由特殊的字符类表示。我们只需要匹配Java标识符即可使用"(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"。优雅,这就是Java! - Alan Moore
@AlanMoore:非常好。不幸的是,RegexBuddy 3.6.2在其Java版本中无法识别这些类。它也无法识别\p{Currency_Symbol},但它可以识别\p{Sc}。我还没有进行更多测试,但我必须这样做,因为RegexBuddy对我的工作流程非常重要。 - aliteralmind
1
RegexBuddy可以处理这个正则表达式:([\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*\.)+ - aliteralmind
显示剩余10条评论

9

这里是一个完全可行的类和测试,基于@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 = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
    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 这个名称不太合适,因为这个模式代表一个完全限定类名。我建议提取 String ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*" 以使其更加明显和易读。 - TWiStErRob
@TWiStErRob,当您说VALID_JAVA_IDENTIFIER代表FQCN时,不确定您的意思是什么?此外,不确定ID_PATTERN更易读...感谢您的解释。 - Renaud
一个有效的Java标识符可以是方法名、局部变量、类名、子包名等。然而,你的“VALID_JAVA_IDENTIFIER”模式匹配多个标识符(每个子包+类名一个)组成的完全限定类名(FQCN)。FQCN不是有效的Java标识符,因为它包含点号。有关“ID_PATTERN”的信息,请参见我对Jörgen答案的编辑;这样更容易看到重复的内容以及何时重复,也不需要滚动或换行。 - TWiStErRob

9

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

为了优化它,您可以将前半部分与后半部分互换。请注意,您还需要更改点匹配。

以下是我修改后的版本,与原始版本相比,运行速度约快两倍:

String ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
Pattern FQCN = Pattern.compile(ID_PATTERN + "(\\." + ID_PATTERN + ")*");

我无法编写注释,因此我决定编写回答。


3

我自己也得出了类似的答案(和Tomalak的答案相似),即M.M.M.N:

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

Where,

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

然而,与Tomalak的答案不同,该正则表达式做出了更多的假设:

  1. 包名(M部分)只能使用小写字母,M的第一个字符始终为小写字母,其余部分可以混合使用下划线、小写字母和数字。

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

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

希望这有所帮助。


0

一个工作正则表达式的简短版本:

\p{Alnum}[\p{Alnum}._]+\p{Alnum}

0

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


0
以下类验证提供的软件包名称是否有效:
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;
    }
}

-1

以下表达式对我来说完全正常运行。

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

大小写敏感。 - Ilya Kharlamov

-3

我会说类似于([\w]+\.)*[\w]+这样的话。

但是,如果你告诉我你想用它做什么,我可能可以更具体一些;)


你不需要使用 [],这个正则表达式 (\\w+\\.?)+ 就足够了。 - Johan Sjöberg
我认为[]可以使事情更清晰,正则表达式已经够乱的了;我将最后一部分放在外面,以清楚地区分包名和类名。 - krtek
我想使用Hibernate Validator(通过@Pattern注释样式)来检查给定的输入是否是一个好的Java类名(完全限定包)。 - Chun ping Wang

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