关于此的对话似乎与命名interface 和abstract 类的对话相反。我发现这令人震惊,并认为这个决定比简单地选择一个命名约定并始终与static final 一起使用要深入得多。
抽象与接口
在命名接口和抽象类时,已接受的约定已演变为不在您的 abstract class 或 interface 前缀或后缀加上任何表明它不是类的任何标识信息。
public interface Reader {}
public abstract class FileReader implements Reader {}
public class XmlFileReader extends FileReader {}
据说开发者不需要知道上面的类是abstract还是interface。
静态决赛
我个人的偏好和信念是,在引用static final 变量时,我们应该遵循类似的逻辑。相反,我们在确定如何命名时评估它的用法。似乎全大写的参数是从 C 和 C++ 语言中盲目采用的东西。在我看来,这不是延续 Java 传统的理由。
意图问题
我们应该问自己static final 在我们自己的上下文中的作用是什么。以下是static final 如何在不同的上下文中使用的三个示例:
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 可见性的立场,因为它更像是public 访问的一种形式,仅限于package 级别。
目的:枚举
现在你可能会说,你为什么使用static final 整数作为enum?那是discussion that is still evolving,我什至会说是有争议的,所以我会尽量不要通过冒险来长时间地破坏这个讨论。但是,建议您可以实现以下公认的枚举模式:
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->int 和int->enum 的相同目的。在通过网络流式传输这些信息的范围内,本机 Java 序列化实在是太冗长了。一个简单的int、short 或byte 可以节省大量带宽。我可以深入比较和对比enum 与static 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 removes the need for 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 只是一个具有所有公共成员且没有构造函数的类。这只是一个数据structure。所以,你可以像时尚一样在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 个变量作为构造函数参数。也就是说,访问(在这种情况下设置)这个structs 成员看起来像:
MyFileHeader header = new MyFileHeader();
header.fileType = buffer.getInt();
header.version = buffer.getInt();
header.dataSize = buffer.getInt();
header.checksumSize = buffer.getInt();
这些不是static 或final,但它们是可以直接设置的公开暴露成员。出于这个原因,我认为当static final 成员公开暴露时,将其完全大写是有意义的。这是将其与公共的非静态变量区分开来很重要的一次。
注意:即使在这种情况下,如果开发人员尝试设置 final 变量,他们也会遇到 IDE 或编译器错误。
总结
总之,您为static final 变量选择的约定将是您的偏好,但我坚信使用上下文应该对您的设计决策产生重大影响。我个人的建议是遵循以下两种方法之一:
方法 1:评估上下文和意图[highly subjective; logical]
- 如果它是一个
private 变量,应该与private 实例变量无法区分,那么将它们命名为相同。 全部小写
- 如果打算用作
static 值的松散enum 样式块,则将其命名为enum。 pascal case:每个单词的首字母大写
- 如果打算定义一些可公开访问的常量和静态属性,则将其设为全部大写以使其脱颖而出
方法 2:私有与公共[objective; logical]
方法 2 基本上将其上下文浓缩为可见性,并且没有解释的余地。
- 如果是
private 或protected,那么它应该全部小写。
- 如果是
public 或package,那么它应该全部大写。
结论
这就是我如何看待static final 变量的命名约定。我不认为它可以或应该被打包成一个单一的包罗万象。我认为你应该在决定如何命名之前评估它的意图。
但是,主要目标应该是努力保持一致
在整个项目/包的范围内。最后,这就是你所能控制的一切。
(我确实希望会遇到阻力,但也希望在这种方法上获得社区的一些支持。无论你的立场是什么,在谴责、批评或赞扬这种风格选择时,请保持文明。)