Java静态常量变量的命名规范

117

有一条规则是这样说的:

表示常量(final 变量)的名称必须全部大写,并使用下划线分隔单词 (来自http://geosoft.no/development/javastyle.html

对于像 int 或 string 这样的基本类型,这很有效:

private static final int MAX_COUNT = 10;

但是对于非原始类型呢?在我看到的大多数情况下,如下所示:

private static final Logger log = Logger.getLogger(MyClass.class);

或者在单例中,实例变量不是大写字母。

问题是声明这些类型的变量(如log和instance)的正确方法是什么?


3
我通常称之为“LOG”,但我的同事似乎不喜欢像这样命名:LOG.info(“LOG not log!”):) - Thomas
3
通常我会将它命名为“LOGGER”。它是一个常量。 - Christoffer Hammarström
4
这不是规则,而是一种公约惯例,像所有的一致性一样,它是软弱心灵的避难所。 Java 中有许多需要忽略的约定--这就是其中之一。它基本上是向匈牙利命名法(一直以来都是一个非常糟糕的想法)致敬。例如,当您决定此项不再是常量(static final)时会发生什么?如果我们幸运的话,我们有像IntelliJ或Eclipse这样的重构工具,可以更改符号的每个用法,但如果我们不幸的话,我们只有vim,而这样的更改就会变得非常痛苦。 - Software Engineer
18
代码的一致性是为那些稍后需要阅读我的代码的人提供庇护的,他们不应该需要了解我认为自己比社区共识更聪明的所有原因。 - gladed
4
@工程师Dollery,编码规范是为了更好地理解他人的程序,而不是针对“软弱”的人。你不应该不尊重那些无法从你的角度看到事物的人。https://en.wikipedia.org/wiki/Coding_conventions - Arash
显示剩余3条评论
9个回答

80

这仍然是一个 常量。有关常量命名惯例的更多信息,请参见JLS。但实际上,这完全取决于个人偏好。


接口类型中的常量名称应为一个或多个单词、缩写或首字母缩写的序列,全大写,用下划线"_"字符分隔。类类型的final变量通常也可以按照此约定命名。常量名称应具有描述性,不应过度缩写。通常它们可以是任何适当的词性。常量名称示例包括MIN_VALUEMAX_VALUEMIN_RADIXMAX_RADIX等,都属于Character类。

表示集合的一组常量或者更少使用的,整数值掩码位的备选值,有时可以使用共同的首字母缩写作为名称前缀来指定,例如:

interface ProcessStates {
  int PS_RUNNING = 0;
  int PS_SUSPENDED = 1;
}

对常量名称的遮蔽是很少见的:

  • 常量名称通常不含小写字母,因此它们通常不会遮蔽包或类型的名称,也不会遮蔽字段,因为字段的名称通常至少包含一个小写字母。
  • 常量名称不能遮蔽方法名称,因为它们在语法上是有区别的。

9
“...类类型的最终变量可能会惯例性地被…” - Andy Thomas
1
它们本身就是字段,那么为什么“隐藏字段”应该被考虑呢?我可以看出来关于隐藏类型名称的观点,但你不能通过IDE/编译器处理不会发生冲突的碰撞。比如你有一个class Error和一个static final int Error = 0;,哪里会发生冲突呢?你不能将Error实例分配给int变量。你不能将int分配给Error变量。在这种情况下,我不认为会发生冲突。你能提供一个真正的示例,其中类型共享相同的名称会导致真正的冲突吗? - crush
1
@crush:在你的例子中,碰撞会发生在代码读者的头脑中。换句话说,这是毫无意义的混淆。我可以看到一些静态常量使用lowerCamelcase字段名称。我也曾这样做过。我知道惯例是大写字母。 - Gilbert Le Blanc
4
void method() { final int VALUE = 5; /* ... */ } 中的 final static 局部 变量是否适用于大写约定? - code_dredd
1
@mre,是的,那就是我最终做的。感谢您的建议。我只是觉得为此发布一个完整的问题可能有些过头了。 - code_dredd
显示剩余4条评论

58

关于此事的对话似乎与命名interfaceabstract类的对话相反。我认为这令人担忧,并认为这个决定比简单选择一个命名约定并始终使用它与static final一起深入。

抽象类和接口

在命名接口和抽象类时,公认的约定已经演变成不使用任何标识信息来作为前缀或后缀,以表明其不同于其他类的是一个abstract classinterface

public interface Reader {}
public abstract class FileReader implements Reader {}
public class XmlFileReader extends FileReader {}

据说开发人员不需要知道以上类是抽象的还是一个接口

静态常量

我个人的偏好和信仰是,当涉及到静态常量变量时,我们应该遵循类似的逻辑来确定如何命名它。似乎全大写的参数是从C和C++语言中盲目地采用的一种传统。在我的评估中,这不是继续在Java中保留这种传统的正当理由。

意图问题

我们应该问自己静态常量在自己的上下文中的作用是什么。以下是三个例子,说明了在不同的上下文中如何使用静态常量

public class ChatMessage {
    //Used like a private variable
    private static final Logger logger = LoggerFactory.getLogger(XmlFileReader.class);

    //Used like an Enum
    public class Error {
        public static final int Success = 0;
        public static final int TooLong = 1;
        public static final int IllegalCharacters = 2;
    }

    //Used to define some static, constant, publicly visible property
    public static final int MAX_SIZE = Integer.MAX_VALUE;
}

在这三种情况下都使用大写字母吗?当然可以,但我认为这可能会削弱每种情况的目的。因此,让我们逐个检查每种情况。


目的:私有变量

以上面的Logger示例为例,记录器被声明为私有变量,并且只会在类内部或可能的内部类中使用。即使它被声明为protected package可见性,其用法也是相同的:

public void send(final String message) {
    logger.info("Sending the following message: '" + message + "'.");
    //Send the message
}

我们不关心logger是一个static final的成员变量,它也可以只是一个final实例变量。我们并不知道具体情况,也无需了解。我们只需要知道使用类实例提供的记录器来记录消息即可。

public class ChatMessage {
    private final Logger logger = LoggerFactory.getLogger(getClass());
}

在这种情况下,你不会把它命名为 LOGGER,那么如果它是 static final,为什么要全部大写命名呢?在两种情况下,它的上下文或意图是相同的。

注意:我改变了关于 package 可见性的立场,因为它更像是一种受限于 package 级别的公共访问形式。


目的:枚举

现在你可能会问,为什么要使用 static final 整数作为 enum?这是一个仍在不断发展的讨论,我甚至可以说是半具争议性的,所以我将尽量不深入探讨,避免偏离此讨论。然而,建议您可以实现以下接受的枚举模式:

public enum Error {
    Success(0),
    TooLong(1),
    IllegalCharacters(2);

    private final int value;

    private Error(final int value) {
        this.value = value;
    }

    public int value() {
        return value;
    }

    public static Error fromValue(final int value) {
        switch (value) {
        case 0:
            return Error.Success;
        case 1:
            return Error.TooLong;
        case 2:
            return Error.IllegalCharacters;
        default:
            throw new IllegalArgumentException("Unknown Error value.");
        }
    }
}
以上有多种变化,都能达到允许显式转换enum -> intint -> enum的同样目的。在网络流传输这些信息时,本地Java序列化过于冗长。使用简单的intshortbyte可以节省大量带宽。我可以详细比较enumstatic final int之间的优缺点,包括类型安全性、可读性、可维护性等方面;幸运的是,这超出了本讨论的范围。

归根结底,有时候static final int会被用作enum风格的结构。

如果你能接受上述陈述为真,我们就可以继续讨论风格问题。在声明enum时,通常不会这样做:
public enum Error {
    SUCCESS(0),
    TOOLONG(1),
    ILLEGALCHARACTERS(2);
}

相反,我们执行以下操作:

public enum Error {
    Success(0),
    TooLong(1),
    IllegalCharacters(2);
}

如果你的 static final 整数块用作松散的 enum,那么为什么要为其使用不同的命名约定?无论在哪种情况下,它的上下文或意图都是相同的。


目的:静态、常量、公共属性

这个用例可能是最模糊和有争议的。静态常量 size 的用法示例是其中最常见的。Java 消除了需要使用 sizeof() 的情况,但有时需要知道数据结构将占用多少字节。

例如,考虑你正在向二进制文件中写入或读取一系列数据结构,并且该二进制文件的格式要求在实际数据之前插入数据块的总大小。这很常见,以便于读取者知道在后续存在不相关的数据时数据何时停止。考虑以下虚构的文件格式:

File Format: MyFormat (MYFM) for example purposes only
[int filetype: MYFM]
[int version: 0] //0 - Version of MyFormat file format
[int dataSize: 325] //The data section occupies the next 325 bytes
[int checksumSize: 400] //The checksum section occupies 400 bytes after the data section (16 bytes each)
[byte[] data]
[byte[] checksum]

这个文件包含一系列通过序列化成字节流并写入该文件的MyObject对象。该文件包含325个MyObject对象的字节,但是不知道每个MyObject的大小,你就无法知道哪些字节属于每个MyObject。因此,你需要在MyObject中定义MyObject的大小:

public class MyObject {
    private final long id; //It has a 64bit identifier (+8 bytes)
    private final int value; //It has a 32bit integer value (+4 bytes)
    private final boolean special; //Is it special? (+1 byte)

    public static final int SIZE = 13; //8 + 4 + 1 = 13 bytes
}

在上述定义中,MyObject数据结构被写入文件时占据13个字节。知道这一点后,当我们读取二进制文件时,可以动态地确定在文件中跟随多少个MyObject对象:

int dataSize = buffer.getInt();
int totalObjects = dataSize / MyObject.SIZE;
这似乎是所有全大写的static final常量的典型用例和参数,我同意在这种情况下,全部大写是有意义的。原因如下:
Java没有像C语言那样的struct类,但是struct只是一个所有成员都是公共的、没有构造函数的类。它只是一种数据结构。因此,您可以以struct的方式声明一个class
public class MyFile {
    public static final int MYFM = 0x4D59464D; //'MYFM' another use of all uppercase!

    //The struct
    public static class MyFileHeader {
        public int fileType = MYFM;
        public int version = 0;
        public int dataSize = 0;
        public int checksumSize = 0;
    }
}

首先声明,我个人不会以这种方式解析。我建议使用一个不可变的类,该类通过接受ByteBuffer或所有4个变量作为构造函数参数在内部处理解析。话虽如此,在这种情况下访问(在这种情况下设置)此struct的成员可能看起来像:

MyFileHeader header = new MyFileHeader();
header.fileType     = buffer.getInt();
header.version      = buffer.getInt();
header.dataSize     = buffer.getInt();
header.checksumSize = buffer.getInt();

这些不是staticfinal,但它们是公开暴露的成员,可以直接设置。因此,我认为当一个static final成员公开显示时,将其全部大写是有意义的。这是唯一需要与公共非静态变量区分的重要时刻。

注意:即使在这种情况下,如果开发人员尝试设置final变量,他们也会遇到IDE或编译器错误。


摘要

总之,您选择的static final变量约定将成为您的首选项,但我坚信使用环境应在设计决策中占主导地位。我的个人建议是遵循以下两种方法之一:

方法1:评估上下文和意图[高度主观;逻辑]

  • 如果是应该与私有实例变量无法区分的private变量,则将它们命名为相同的名称。全部小写
  • 如果它的意图是作为一组static值的松散枚举风格块,则将其命名为enum帕斯卡命名法:每个单词首字母大写
  • 如果其意图是定义一些公开可访问的、常量和静态属性,则通过使其全部大写来突出显示它

方法2:私有 vs 公共[客观;逻辑]

方法2将其上下文压缩为可见性,并且不留任何解释余地。

  • 如果它是privateprotected,则应全部小写
  • 如果它是publicpackage,则应全部大写

结论

这就是我看待static final变量命名约定的方式。我认为它不是可以或应该被装箱成一个单独的全部捕获的东西。我相信您应该在决定如何命名之前评估其意图。

但是,主要目标应尽力在整个项目/包的范围内保持一致。最终,这是您所掌控的所有内容。

(我预计会遭到反抗,但也希望从社区中获得对这种方法的支持。无论您的立场如何,请在反驳、批评或赞扬这种风格选择时保持礼貌。)


4
下投票导致未在所有地方大写。 - Nikola Yovchev
2
@baba所以被踩是因为没有遵循那些被现代开发环境淘汰的盲目惯例?逻辑很好。 - crush
2
@crush,常量始终大写,无论它们的类型和可见性如何,无论它们是原始类型还是其他类型。这就是语言规范指定的方式,也应该是应该的方式。IDE 与此无关。日志记录器不是常量,因为它们具有功能,因此不应大写。看,很容易,最重要的是符合标准。 - Nikola Yovchev
2
@NikolaYovchev 你为什么需要通过命名约定知道一个变量是常量?仅仅因为标准这样规定并不是不进化的好理由。显然我知道标准的规定,但我选择提供一种偏离标准的方法,因为我们已经超越了需要名称来检查变量特征的时代。 - crush
2
尝试在运行时修改静态的最终记录器,看看会发生什么。你忽略了我的问题。 - LegendLength
显示剩余12条评论

15

语言并不在乎,重要的是遵循您正在工作的项目所建立的样式和惯例,这样其他维护者(或从现在起五个月后的您)就有最好的机会不被混淆。

我认为一个可变对象的全大写名称肯定会让我感到困惑,即使对该对象的引用恰好存储在一个static final变量中。


12

一个对象的常量引用并不是一个常量,它只是一个对象的常量引用。

private static final并不能定义某个东西是否为常量。这只是Java中定义常量的方式,但并不意味着每个private static final声明都是用来定义常量的。

当我写private static final Logger时,我并不是在试图定义一个常量,我只是在试图定义一个对对象的引用,该引用是private(不能从其他类访问),static(它是一个类级别变量,无需实例)和final(只能分配一次)。如果恰好与Java期望您声明常量的方式重合,那就太倒霉了,但这并不会使其成为常量。我不在乎编译器、Sonar或任何Java大师说什么。 像MILLISECONDS_IN_A_SECOND = 1000这样的常量值是一回事,而对象的常量引用则是另一回事。

黄金被认为是闪耀的,但并不是所有闪耀的东西都是黄金。


没有staticfinal是否也是常量?如果不是,那它应该被称为什么? - Kartik Chugh
我认为你的意思是如果我愿意,我可以称之为常量--但我的真正问题是,我是否应该将final变量大写。 - Kartik Chugh
7
旧有的常量概念已经丢失。以前,常量指向实际固定的数值。现在你可以使用常量引用一个可以改变其状态的对象。如果对你来说这是一个常量,请称它为常量(并将其大写)。如果不是,那就不要称之为常量。这基本上取决于你将给予该对象的处理方式以及你对它的期望。把每个私有静态 final 引用都认为是常量,仅仅根据它的签名是错误的。因此,我也不会将所有 final 变量称为常量。如果这个 final 变量在你的脑海中是一个常量,那么请这样做。 - drakorg

8

这是一个非常有趣的问题。根据变量类型,我会将你问题中的两个常量分为两类。int MAX_COUNT 是原始类型的常量,而 Logger log 是非原始类型的常量。

当我们使用原始类型的常量时,我们只在代码中更改一次该常量的值 public static final in MAX_COUNT = 10,然后在其他地方仅访问该常量的值 for(int i = 0; i<MAX_COUNT; i++)。这就是我们习惯使用这种约定的原因。

而对于非原始类型的常量,尽管我们只在一个地方初始化该常量 private static final Logger log = Logger.getLogger(MyClass.class);,但我们期望在其他地方修改或调用此常量的方法 log.debug("Problem")。 我们不喜欢在大写字母后面加点号。毕竟,我们必须在点运算符之后放置函数名,这肯定会是一个驼峰命名法的名称。这就是为什么 LOG.debug("Problem") 看起来很奇怪的原因。

String 类型也是同样的情况。我们通常不会在代码中更改或调用 String 常量的方法,因此我们使用大写命名约定来表示 String 类型对象。


4

没有"正确"的方法——只有惯例。您已经陈述了最常见的惯例,这也是我在自己的代码中遵循的惯例:所有静态常量都应该用大写字母表示。我想其他团队遵循其他惯例。


出于历史原因,我会将简单的最终静态类型(如字符串和数字)标记为大写字母。我来自C/C++,在那里常量通常用大写字母标记。 - Michael Shopsin

3

在我看来,变量被称为“常量”通常是一种实现细节,并不一定需要不同的命名约定。这样做可能有助于可读性,但在某些情况下也可能会损害可读性。


那是一个惯例,你可以选择使用或不使用。但你遵循它的程度越高,效果就越好。 - Cristiano Fontes

1

这些变量是常量,即使它们的名称没有全大写,也是private static final。全大写的命名约定只是为了更明显地表明这些变量是常量,但并不是必需的。我见过

private static final Logger log = Logger.getLogger(MyClass.class);

在之前使用小写字母,我觉得没问题,因为我知道只有使用记录器来记录消息,但这违反了约定。你可以说将其命名为log是一个子约定,我想。但总的来说,在常量命名中使用大写并不是唯一正确的方式,但它是最好的方式。


“最佳方式”仍然是主观的 - 这是我对你在这个问题上的回答所持有的唯一异议。例如,我并不认为这是最好的方式。我的个人偏好是static final变量应该与其他变量无法区分。就像您不会给接口加前缀IReader或后缀ReaderInterface一样。相反,您将其命名为Reader,因为实现细节对开发人员并不重要。在记录器的情况下,基于名称,开发人员是否真的需要知道或关心logger是静态的还是非静态的?我认为不需要。 - crush
确实,将常量命名为全大写有点像匈牙利命名法的一种实例(例如,对于int,使用iMyVar,对于string,使用sMyVar等)。因此,我可以理解不使用大写字母来节省打字时间。但是,我认为程序员知道特定变量是“static final”的价值在于,如果他们尝试修改它,他们会收到错误提示。我相信这样做从长远来看可以节省时间。 - andronikus
编译器和IDE应该/将会通知他们正在尝试设置一个final变量的值。我确实认为这对于公开访问的变量是有帮助的。请参见下面我的论点。 - crush
一个常量值和一个指向对象的常量引用不是同一件事情。即使是被常量引用所引用的对象也不是常量,它仍然可以根据公开的方法进行更改。因此,你所谓的常量并没有任何不变的东西。另一方面,一个真正的常量可能是 MILLISECONDS_IN_A_SECOND = 1000。 - drakorg

0

不要过于狂热地遵循SUN制定的规则,而是按照你和你的团队的感觉去做。

例如,这就是eclipse打破惯例的方式。尝试添加implements Serializable,eclipse会要求为您生成此行。

更新:有一些特殊情况被排除在外,我不知道这一点。然而,我仍然坚持按照你和你的团队的意愿去做。


1
那个名称实际上是Java序列化规范的一部分;这是官方认可的打破惯例! - Ernest Friedman-Hill
有趣,我不知道那个。 - user425367

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