如何验证一个字符串是否为有效的Java变量?

15

如何最好地检查一个字符串输入是否是一个有效的Java变量以进行编码?我相信我不是第一个想要这么做的人。但也许我错过了寻找有用信息的正确关键字。

最好的方法可能是使用正则表达式来检查以下内容:

  • 以字母开头

  • 然后可以包含数字、字母

  • 可以包含一些特殊字符,比如 '_' (是哪些?)

  • 不允许包含空格分隔符

1
我想知道你为什么需要那个... - John Dvorak
4
自动生成代码(或验证)? - NPE
https://dev59.com/RGgt5IYBdhLWcg3w2Q-U - John Dvorak
2
Jan:那是一个完全不同的问题。 - Joey
我重新打开了这个问题,因为显然的重复实际上并不是一个重复。 - Duncan Jones
如果您正在使用Java 6+,请考虑使用javax.lang.model.SourceVersion类。它具有用于此类操作的方法。 - Amir Pashazadeh
2个回答

20
public static boolean isValidJavaIdentifier(String s) {
    if (s.isEmpty()) {
        return false;
    }
    if (!Character.isJavaIdentifierStart(s.charAt(0))) {
        return false;
    }
    for (int i = 1; i < s.length(); i++) {
        if (!Character.isJavaIdentifierPart(s.charAt(i))) {
            return false;
        }
    }
    return true;
}

编辑:正如@Joey所指出的,您还应该过滤关键字和保留字。


6
您缺少关键字“true”、“false”和“null”。它们必须被禁止使用。但是,除此之外,那正是规范建议的做法;-) - Joey
1
不是那么多:http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.9 - assylias
第一个if语句可能需要进行空值检查和额外的。此外,字符串中的_会使代码出错,因为它没有第二个字符。 - eis
这让我朝着一个不错的方向开始了。对于我的情况来说,关键字检查并不是那么重要,但可能很容易通过提供的链接进行扩展。 - membersound
1
@eis:我认为将null传递给此类方法是一个错误,因此我希望在这里出现NPE。我不理解您评论的第二部分:_是一个有效的Java标识符。 - JB Nizet
显示剩余2条评论

16

Java 6+

使用

import javax.lang.model.SourceVersion;

boolean isValidVariableName(CharSequence name) {
    return SourceVersion.isIdentifier(name) && !SourceVersion.isKeyword(name);
}

如果您需要检查一个字符串是否为Java的合法变量名,在最新版本的Java中,或者

import javax.lang.model.SourceVersion;

boolean isValidVariableNameInVersion(CharSequence name, SourceVersion version) {
    return SourceVersion.isIdentifier(name) && !SourceVersion.isKeyword(name, version);
}

如果您需要检查一个字符串在特定的Java版本中是否是有效的Java变量名称。

例如,从Java 9开始,下划线成为了保留关键字,因此isValidVariableNameInVersion("_", SourceVersion.RELEASE_9)将返回false,而isValidVariableNameInVersion("_", SourceVersion.RELEASE_8)则将返回true

工作原理

SourceVersion.isIdentifier(CharSequence name)检查name是否为最新源版本中的语法上有效的标识符(简单名称)或关键字。!SourceVersion.isKeyword(name)对于关键字返回false。因此,SourceVersion.isIdentifier(name)&&!SourceVersion.isKeyword(name)仅对有效的标识符返回true。

内置方法SourceVersion.isName(CharSequence name, SourceVersion version)也使用相同的方法来检查name是否是语法上有效的限定名称,这意味着它将对类似于“apple.color”的字符串返回true

public static boolean isName(CharSequence name, SourceVersion version) {
    String id = name.toString();

    for(String s : id.split("\\.", -1)) {
        if (!isIdentifier(s) || isKeyword(s, version))
            return false;
    }
    return true;
}

测试

import org.junit.jupiter.api.Test;

import javax.lang.model.SourceVersion;

import static org.assertj.core.api.Assertions.assertThat;

public class ValidVariableNameTest {
    boolean isValidVariableName(CharSequence name) {
        return isValidVariableNameInVersion(name, SourceVersion.RELEASE_8);
    }

    boolean isValidVariableNameInVersion(CharSequence name, SourceVersion version) {
        return SourceVersion.isIdentifier(name) && !SourceVersion.isKeyword(name, version);
    }

    @Test
    void variableNamesCanBeginWithLetters() {
        assertThat(isValidVariableName("test")).isTrue();
        assertThat(isValidVariableName("e2")).isTrue();
        assertThat(isValidVariableName("w")).isTrue();
        assertThat(isValidVariableName("привет")).isTrue();
    }

    @Test
    void variableNamesCanBeginWithDollarSign() {
        assertThat(isValidVariableName("$test")).isTrue();
        assertThat(isValidVariableName("$e2")).isTrue();
        assertThat(isValidVariableName("$w")).isTrue();
        assertThat(isValidVariableName("$привет")).isTrue();
        assertThat(isValidVariableName("$")).isTrue();
        assertThat(isValidVariableName("$55")).isTrue();
    }

    @Test
    void variableNamesCanBeginWithUnderscore() {
        assertThat(isValidVariableName("_test")).isTrue();
        assertThat(isValidVariableName("_e2")).isTrue();
        assertThat(isValidVariableName("_w")).isTrue();
        assertThat(isValidVariableName("_привет")).isTrue();
        assertThat(isValidVariableName("_55")).isTrue();
    }

    @Test
    void variableNamesCannotContainCharactersThatAreNotLettersOrDigits() {
        assertThat(isValidVariableName("apple.color")).isFalse();
        assertThat(isValidVariableName("my var")).isFalse();
        assertThat(isValidVariableName(" ")).isFalse();
        assertThat(isValidVariableName("apple%color")).isFalse();
        assertThat(isValidVariableName("apple,color")).isFalse();
        assertThat(isValidVariableName(",applecolor")).isFalse();
    }

    @Test
    void variableNamesCannotStartWithDigit() {
        assertThat(isValidVariableName("2e")).isFalse();
        assertThat(isValidVariableName("5")).isFalse();
        assertThat(isValidVariableName("123test")).isFalse();
    }


    @Test
    void differentSourceVersionsAreHandledCorrectly() {
        assertThat(isValidVariableNameInVersion("_", SourceVersion.RELEASE_9)).isFalse();
        assertThat(isValidVariableNameInVersion("_", SourceVersion.RELEASE_8)).isTrue();

        assertThat(isValidVariableNameInVersion("enum", SourceVersion.RELEASE_9)).isFalse();
        assertThat(isValidVariableNameInVersion("enum", SourceVersion.RELEASE_4)).isTrue();
    }

    @Test
    void keywordsCannotBeUsedAsVariableNames() {
        assertThat(isValidVariableName("strictfp")).isFalse();
        assertThat(isValidVariableName("assert")).isFalse();
        assertThat(isValidVariableName("enum")).isFalse();

        // Modifiers
        assertThat(isValidVariableName("public")).isFalse();
        assertThat(isValidVariableName("protected")).isFalse();
        assertThat(isValidVariableName("private")).isFalse();

        assertThat(isValidVariableName("abstract")).isFalse();
        assertThat(isValidVariableName("static")).isFalse();
        assertThat(isValidVariableName("final")).isFalse();

        assertThat(isValidVariableName("transient")).isFalse();
        assertThat(isValidVariableName("volatile")).isFalse();
        assertThat(isValidVariableName("synchronized")).isFalse();

        assertThat(isValidVariableName("native")).isFalse();

        // Declarations
        assertThat(isValidVariableName("class")).isFalse();
        assertThat(isValidVariableName("interface")).isFalse();
        assertThat(isValidVariableName("extends")).isFalse();
        assertThat(isValidVariableName("package")).isFalse();
        assertThat(isValidVariableName("throws")).isFalse();
        assertThat(isValidVariableName("implements")).isFalse();

        // Primitive types and void
        assertThat(isValidVariableName("boolean")).isFalse();
        assertThat(isValidVariableName("byte")).isFalse();
        assertThat(isValidVariableName("char")).isFalse();
        assertThat(isValidVariableName("short")).isFalse();
        assertThat(isValidVariableName("int")).isFalse();
        assertThat(isValidVariableName("long")).isFalse();
        assertThat(isValidVariableName("float")).isFalse();
        assertThat(isValidVariableName("double")).isFalse();
        assertThat(isValidVariableName("void")).isFalse();

        // Control flow
        assertThat(isValidVariableName("if")).isFalse();
        assertThat(isValidVariableName("else")).isFalse();

        assertThat(isValidVariableName("try")).isFalse();
        assertThat(isValidVariableName("catch")).isFalse();
        assertThat(isValidVariableName("finally")).isFalse();

        assertThat(isValidVariableName("do")).isFalse();
        assertThat(isValidVariableName("while")).isFalse();
        assertThat(isValidVariableName("for")).isFalse();
        assertThat(isValidVariableName("continue")).isFalse();

        assertThat(isValidVariableName("switch")).isFalse();
        assertThat(isValidVariableName("case")).isFalse();
        assertThat(isValidVariableName("default")).isFalse();
        assertThat(isValidVariableName("break")).isFalse();
        assertThat(isValidVariableName("throw")).isFalse();

        assertThat(isValidVariableName("return")).isFalse();

        // Other keywords
        assertThat(isValidVariableName("this")).isFalse();
        assertThat(isValidVariableName("new")).isFalse();
        assertThat(isValidVariableName("super")).isFalse();
        assertThat(isValidVariableName("import")).isFalse();
        assertThat(isValidVariableName("instanceof")).isFalse();

        // Reserved keywords
        assertThat(isValidVariableName("goto")).isFalse();
        assertThat(isValidVariableName("const")).isFalse();
    }

    @Test
    void literalsCannotBeUsedAsVariableNames() {
        assertThat(isValidVariableName("null")).isFalse();
        assertThat(isValidVariableName("true")).isFalse();
        assertThat(isValidVariableName("false")).isFalse();
    }
}

这里使用 SourceVersion.isIdentifier() 方法会更加适合,不是吗? - Sebastian
同意,看起来"SourceVersion.isIdentifier()"对于像"my.var"这样的字符串表现更好。 - Christophe Moine
@ChristopheMoine @Sebastian 非常感谢你们指出我的答案中的一个错误。我已经修复了它,并且大大扩展了答案。请注意,SourceVersion.isIdentifier() 单独不能用于检查字符串是否为有效的 Java 变量,因为它对关键字(如 nullinttruefalse)返回 true,这些关键字不能用作 Java 变量名。 - Denis Stafichuk

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