1. Introduction
1.绪论
In this article, we’re going to learn about using constants in Java with a focus on common patterns and anti-patterns.
在这篇文章中,我们将学习在Java中使用常量的知识,重点是常见的模式和反模式。
We’ll start with some basic conventions for defining constants. From there, we’ll move onto common anti-patterns before finishing with a look at common patterns.
我们将从定义常量的一些基本约定开始。从那里,我们将转向常见的反模式,最后再看一下常见的模式。
2. Basics
2.基础知识
A constant is a variable whose value won’t change after it’s been defined.
常数是一个变量,其值在被定义后不会改变。
Let’s look at the basics for defining a constant:
我们来看看定义常数的基本知识。
private static final int OUR_CONSTANT = 1;
Some of the patterns we’ll look at will address the public or private access modifier decision. We make our constants static and final and give them an appropriate type, whether that’s a Java primitive, a class, or an enum. The name should be all capital letters with the words separated by underscores, sometimes known as screaming snake case. Finally, we provide the value itself.
我们要研究的一些模式将解决public或privateaccess modifier的问题。我们使我们的常量static和final,并给它们一个适当的类型,无论是Java原语、类,还是enum。名称应该全部是大写字母,单词之间用下划线隔开,有时被称为尖叫的蛇形码。最后,我们提供值本身。
3. Anti-Patterns
3.反模式
First, let’s start by learning what not to do. Let’s look at a couple of common anti-patterns we might encounter when working with Java constants.
首先,让我们从学习不要做什么开始。让我们看看在使用Java常量时可能遇到的几个常见的反模式。
3.1. Magic Numbers
3.1.神奇的数字
Magic numbers are are numeric literals in a block of code:
Magic numbers是代码块中的数字字元:。
if (number == 3.14159265359) {
// ...
}
They’re hard for other developers to understand. Additionally, if we’re using a number throughout our code, it’s difficult to deal with changing the value. We should instead define the number as a constant.
它们对其他开发者来说很难理解。此外,如果我们在整个代码中使用一个数字,就很难处理改变数值的问题。我们应该把数字定义为一个常数。
3.2. A Large Global Constants Class
3.2.一个大型全局常量类
When we start a project, it might feel natural to create a class named Constants or Utils with the intention of defining all the constants for the application there. For smaller projects, this might be ok, but let’s consider a couple of reasons why this isn’t an ideal solution.
当我们开始一个项目时,可能会觉得很自然地创建一个名为Constants或Utils的类,打算在那里定义应用程序的所有常量。对于较小的项目,这可能是可以的,但让我们考虑一下为什么这不是一个理想的解决方案的几个原因。
First, let’s imagine we have a hundred or more constants all in our constants class. If the class isn’t maintained, both to keep up with documentation and to occasionally refactor the constants into logical groupings, it’s going to get pretty unreadable. We could even end up with duplicate constants with slightly different names. This approach is likely to give us readability and maintainability problems in anything but the smallest projects.
首先,让我们想象一下,我们有一百多个常量都在我们的常量类中。如果不对该类进行维护,既要跟上文档的进度,又要偶尔将常量重构为逻辑分组,那么它就会变得非常难读。我们甚至会出现名称略有不同的重复常量。除了最小的项目,这种方法很可能给我们带来可读性和可维护性问题。
In addition to the logistics of maintaining the Constants class itself, we’re also inviting other maintainability problems by encouraging too much interdependency with this one global constants class and various other parts of our application.
除了维护Constants类本身的后勤工作外,我们还鼓励这个全局常量类与我们应用程序的其他各部分之间有太多的相互依赖性,从而招致其他可维护性问题。
On a more technical side, the Java compiler places the value of the constant into referencing variables in the classes in which we use them. So, if we change one of our constants in our constants class and only recompile that class and not the referencing class, we can get inconsistent constant values.
在技术层面上,Java编译器将常量的值放入我们使用它们的类中的引用变量中。因此,如果我们改变了常量类中的一个常量,而只重新编译该类而不是引用类,我们会得到不一致的常量值。
3.3. The Constant Interface Anti-Pattern
3.3.恒定接口的反模式
The constant interface pattern is when we define an interface that contains all of the constants for certain functionality and then have the classes that need those functionalities to implement the interface.
常量接口模式是指我们定义一个包含某些功能的所有常量的接口,然后让需要这些功能的类来实现这个接口。
Let’s define a constant interface for a calculator:
让我们来定义一个计算器的常量接口。
public interface CalculatorConstants {
double PI = 3.14159265359;
double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
enum Operation {ADD, SUBTRACT, MULTIPLY, DIVIDE};
}
Next, we’ll implement our CalculatorConstants interface:
接下来,我们将实现我们的CalculatorConstants接口。
public class GeometryCalculator implements CalculatorConstants {
public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
// Code to do an operation
}
}
The first argument against using a constant interface is that it goes against the purpose of an interface. We’re meant to use interfaces to create a contract for the behavior our implementing classes are going to provide. When we create an interface full of constants, we’re not defining any behavior.
反对使用恒定接口的第一个理由是,它违背了接口的目的。我们应该使用接口来为我们的实现类所要提供的行为创建一个契约。当我们创建一个充满常量的接口时,我们并没有定义任何行为。
Secondly, using a constant interface opens us up to run-time issues caused by field shadowing. Let’s look at how that might happen by defining a UPPER_LIMIT constant within our GeometryCalculator class:
其次,使用一个常量接口会使我们在运行时出现由字段阴影引起的问题。让我们看看在我们的GeometryCalculator类中定义一个UPPER_LIMIT常量会如何发生。
public static final double UPPER_LIMIT = 100000000000000000000.0;
Once we define that constant in our GeometryCalculator class, we hide the value in the CalculatorConstants interface for our class. We could then get unexpected results.
一旦我们在GeometryCalculator类中定义了这个常数,我们就在CalculatorConstants接口中为我们的类隐藏这个值。然后我们可能会得到意想不到的结果。
Another argument against this anti-pattern is that it causes namespace pollution. Our CalculatorConstants will now be in the namespace for any of our classes that implement the interface as well as any of their subclasses.
反对这种反模式的另一个理由是它会造成命名空间污染。我们的CalculatorConstants现在将在我们任何实现该接口的类以及它们的任何子类的命名空间中。
4. Patterns
4.模式
Earlier, we looked at the appropriate form for defining constants. Let’s look at some other good practices for defining constants within our applications.
早些时候,我们看了定义常量的适当形式。让我们来看看在我们的应用程序中定义常量的一些其他良好做法。
4.1. General Good Practices
4.1.一般的良好做法
If constants are logically related to a class, we can just define them there. If we view a set of constants as members of an enumerated type, we can use an enum to define them.
如果常量在逻辑上与一个类相关,我们可以直接在那里定义它们。如果我们将一组常量视为一个枚举类型的成员,我们可以使用enum来定义它们。
Let’s define some constants in a Calculator class:
让我们在一个Calculator类中定义一些常数。
public class Calculator {
public static final double PI = 3.14159265359;
private static final double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
public enum Operation {
ADD,
SUBTRACT,
DIVIDE,
MULTIPLY
}
public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
if (numberOne > UPPER_LIMIT) {
throw new IllegalArgumentException("'numberOne' is too large");
}
if (numberTwo > UPPER_LIMIT) {
throw new IllegalArgumentException("'numberTwo' is too large");
}
double answer = 0;
switch(operation) {
case ADD:
answer = numberOne + numberTwo;
break;
case SUBTRACT:
answer = numberOne - numberTwo;
break;
case DIVIDE:
answer = numberOne / numberTwo;
break;
case MULTIPLY:
answer = numberOne * numberTwo;
break;
}
return answer;
}
}
In our example, we’ve defined a constant for UPPER_LIMIT that we’re only planning on using in the Calculator class, so we’ve set it to private. We want other classes to be able to use PI and the Operation enum, so we’ve set those to public.
在我们的例子中,我们为UPPER_LIMIT定义了一个常量,我们只打算在Calculator类中使用,所以我们将其设置为private。我们希望其他类能够使用PI和Operation枚举,所以我们将它们设置为public。
Let’s consider some of the advantages of using an enum for Operation. The first advantage is that it limits the possible values. Imagine that our method takes a string for the operation value with the expectation that one of four constant strings is supplied. We can easily foresee a scenario where a developer calling the method sends their own string value. With the enum, the values are limited to those we define. We can also see that enums are especially well suited to use in switch statements.
让我们考虑一下为Operation使用enum的一些优点。第一个优点是,它限制了可能的值。想象一下,我们的方法需要一个字符串作为操作值,期望提供四个常量字符串中的一个。我们可以很容易地预见到这样一种情况,即调用该方法的开发者会发送他们自己的字符串值。使用enum,值被限制在我们定义的范围内。我们还可以看到enum特别适合用于switch语句。
4.2. Constants Class
4.2.常量类
Now that we’ve looked at some general good practices, let’s consider the case when a constants class might be a good idea. Let’s imagine our application contains a package of classes that need to do various kinds of mathematical calculations. In this case, it probably makes sense for us to define a constants class in that package for constants that we’ll use in our calculations classes.
现在我们已经看了一些一般的良好做法,让我们考虑一下常量类可能是一个好主意的情况。让我们想象一下,我们的应用程序包含一个需要进行各种数学计算的类包。在这种情况下,我们在该包中为我们将在计算类中使用的常量定义一个常量类可能是合理的。
Let’s create a MathConstants class:
我们来创建一个MathConstants类。
public final class MathConstants {
public static final double PI = 3.14159265359;
static final double GOLDEN_RATIO = 1.6180;
static final double GRAVITATIONAL_ACCELERATION = 9.8;
static final double EULERS_NUMBER = 2.7182818284590452353602874713527;
public enum Operation {
ADD,
SUBTRACT,
DIVIDE,
MULTIPLY
}
private MathConstants() {
}
}
The first thing we should notice is that our class is final to prevent it from being extended. Additionally, we’ve defined a private constructor so it can’t be instantiated. Finally, we can see that we’ve applied the other good practices we discussed earlier in the article. Our constant PI is public because we anticipate needing to access it outside of our package. The other constants we’ve left as package-private, so we can access them within our package. We’ve made all of our constants static and final and named them in a screaming snake case. The operations are a specific set of values, so we’ve used an enum to define them.
我们首先应该注意的是,我们的类是final,以防止它被扩展。此外,我们定义了一个private构造函数,所以它不能被实例化。最后,我们可以看到,我们已经应用了我们在文章前面讨论的其他良好做法。我们的常量PI是public,因为我们预计需要在包之外访问它。其他的常量我们保留为package-private,所以我们可以在包内访问它们。我们把所有的常量都设为static和final,并以尖叫的蛇形案例来命名它们。操作是一组特定的值,所以我们用一个enum来定义它们。
We can see that our specific package-level constants class is different from a large global constants class because it’s localized to our package and contains constants relevant to that package’s classes.
我们可以看到,我们特定的包级常量类与大型的全局常量类不同,因为它是针对我们的包进行本地化的,包含与该包的类相关的常量。
5. Conclusion
5.总结
In this article, we considered the pros and cons of some of the most popular patterns and anti-patterns seen when using constants in Java. We started out with some basic formatting rules, before covering anti-patterns. After learning about a couple of common anti-patterns, we looked at patterns that we often see applied to constants.
在这篇文章中,我们考虑了在Java中使用常量时看到的一些最流行的模式和反模式的利与弊。在介绍反模式之前,我们从一些基本的格式化规则开始。在了解了一些常见的反模式之后,我们看了一些我们经常看到的应用于常量的模式。
As always the code is available over on GitHub.
像往常一样,代码可在GitHub上获得。