1. Overview
1.概述
Utility classes contain only static members that we group together around a specific topic. Thus, the classes themselves are stateless, while their members contain code meant to be reused across several layers.
实用类只包含静态的成员,我们围绕特定的主题进行分组。因此,类本身是无状态的,而其成员包含的代码是可以在多个层中重复使用的。
In this tutorial, we’ll explain why static code analyzers report that utility classes shouldn’t have public constructors. We’ll look at solving that issue by implementing a private constructor. In addition, we’ll explore which Lombok annotations can help us generate one. We’ll also show how to disable these warnings.
在本教程中,我们将解释为什么静态代码分析器报告说实用类不应该有公共构造函数。我们将研究如何通过实现一个私有构造函数来解决这个问题。此外,我们将探讨哪些Lombok注解可以帮助我们生成一个构造函数。我们还将展示如何禁用这些警告。
Finally, we’ll evaluate some alternative approaches towards implementing utility classes in Java.
最后,我们将评估一些在Java中实现实用类的替代方法。
2. Utility Classes
2.公用事业类
Unlike classes that define objects, utility classes don’t save any data or state. They only contain behavior. Utilities contain only static members. All of their methods are static, while data is passed only as method arguments.
与定义对象的类不同,实用类不保存任何数据或状态。它们只包含行为。公用程序只包含静态成员。它们所有的方法都是静态的,而数据只作为方法参数传递。
2.1. Why Utility Classes?
2.1.为什么是实用类?
In object-oriented programming, we’re looking to model our problem domain and group together families of similar functionality.
在面向对象的编程中,我们要对我们的问题域进行建模,并将类似功能的系列组合起来。
We may also choose to write pure functions to model common behavior across our codebase, especially when using functional programming. Unlike object methods, these pure functions are not related to an instance of any object. However, they do need a home. Java doesn’t have a specific type set aside for housing a set of functions, so we often create a utility class.
我们也可以选择编写纯函数来模拟整个代码库的共同行为,特别是在使用函数式编程时。与对象方法不同,这些纯函数与任何对象的实例没有关系。然而,它们确实需要一个家。Java没有一个特定的类型来容纳一组函数,所以我们经常创建一个实用类。
Great examples of popular utility classes in Java are Arrays and Collections from java.util, as well as StringUtils form org.apache.commons.lang3.
Java中流行的实用类的最佳例子是来自java.util的Arrays和Collections,以及org.apache.commons.lang3的StringUtils。
2.2. Implementation in Java
2.2.用Java实现
Java doesn’t provide a special keyword or a way for creating utility classes. Thus, we usually create a utility class as a plain Java class, but with only static members:
Java没有提供一个特殊的关键字,也没有提供创建实用类的方法。因此,我们通常将实用程序类作为普通的Java类来创建,但只包含静态成员。
public final class StringUtils {
public static boolean isEmpty(String source) {
return source == null || source.length() == 0;
}
public static String wrap(String source, String wrapWith) {
return isEmpty(source) ? source : wrapWith + source + wrapWith;
}
}
In our example, we marked the utility class as public and final. Utilities are usually made public as they are intended to be reused across several layers.
在我们的例子中,我们将实用程序类标记为public和final。实用程序通常是公开的,因为它们是要在多个层中重复使用的。
The final keyword prevents subclassing. Since utility classes are not designed for inheritance, we shouldn’t subclass them.
final关键字可以防止子类化。由于实用类不是为继承而设计的,我们不应该对其进行子类化。
2.3. Public Constructor Warning
2.3.公共构造函数警告
Let’s try to analyze our example utility class using SonarQube, a popular static code analysis tool. We can run a SonarQube analysis on a Java project using the build tool plugin, in this case, Maven:
让我们尝试使用SonarQube(一种流行的静态代码分析工具)来分析我们的示例实用类。我们可以使用构建工具插件在Java项目上运行SonarQube分析,本例中是Maven。
mvn clean verify sonar:sonar -Dsonar.host.url=http://localhost:9000 -Dsonar.login=XYXYXYXY
The static code analysis results in a major code smell. SonarQube warns us to hide the implicit public constructor in our utility class:
静态代码分析的结果是一个主要的代码气味。SonarQube警告我们在我们的实用类中隐藏隐式公共构造函数。
Though we didn’t add a constructor to our utility class, Java implicitly added a default public one. Thus, enabling API users to create an instance of it:
虽然我们没有给我们的实用程序类添加构造函数,但Java隐含地添加了一个默认的公共构造函数。因此,使API用户能够创建它的一个实例。
StringUtils utils = new StringUtils();
This is a misuse of our utility classes, as it was not designed to be instantiated. Therefore, the SonarQube rule advises us to add a private constructor in order to hide the default public one.
这是对我们的实用类的误用,因为它不是被设计为实例化的。因此,SonarQube规则建议我们添加一个私有构造函数,以隐藏默认的公共构造函数。
3. Adding a Private Constructor
3.添加一个私有构造函数
Let’s now solve the reported code smell by adding a private constructor in our utility class.
现在让我们通过在我们的实用程序类中添加一个private constructor来解决报告中的代码异味。
3.1. Default Private Constructor
3.1.默认的私有构造函数
Let’s add a private constructor with no arguments to our utility class. We will never really use this private constructor. Thus, it is a good practice to throw an exception in case it is called:
让我们为我们的实用程序类添加一个没有参数的私有构造函数。我们将永远不会真正使用这个私有构造函数。因此,在它被调用时抛出一个异常是一个好的做法。
public final class StringUtils {
private StringUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
// public static methods
}
We should note that the private constructor also cannot be tested. Thus, this approach will result in one line of uncovered code in our code-coverage measurements.
我们应该注意到,私有构造函数也不能被测试。因此,在我们的代码覆盖率测量中,这种方法将导致一行未覆盖的代码。
3.2. Using Lombok NoArgsConstructor
3.2.使用Lombok NoArgsConstructor
We can make use of the NoArgsConstructor Lombok annotation to auto-generate the private constructor:
我们可以利用 NoArgsConstructor Lombok注释来 自动生成私有构造函数。
@NoArgsConstructor(access= AccessLevel.PRIVATE)
public final class StringUtils {
// public static methods
}
This way, we can avoid manually adding an additional line of uncovered code.
这样,我们就可以避免手动添加额外的一行未覆盖的代码。
3.3. Using Lombok UtilityClass
3.3.使用Lombok UtilityClass
We can also use the UtilityClass Lombok annotation that marks an entire class as a utility:
我们也可以使用UtilityClassLombok注解,将整个类标记为一个实用程序。
@UtilityClass
public class StringUtils {
// public static methods
}
In this case, Lombok will automatically:
在这种情况下,龙目岛将自动。
- generate a private constructor that throws an exception
- flag as error any explicit constructors we add
- mark the class final
We should note that, at this time, the UtilityClass annotation is still an experimental feature.
我们应该注意到,在这个时候,UtilityClass注解仍然是一个实验性的功能。
4. Disabling the Warning
4.禁用警告
If we decide not to follow the recommended solution, we also have an option to disable the public constructor warning.
如果我们决定不遵循推荐的解决方案,我们也可以选择禁用公共构造函数警告。
4.1. Suppressing Warning
4.1.抑制警告
Let’s make use of Java’s SuppressWarnings annotation in order to disable the warning on a single class level:
让我们利用Java的SuppressWarnings注解,以便在单个类上禁用警告。
@SuppressWarnings("java:S1118")
public final class StringUtils {
// public static methods
}
We should pass the correct SonarQube rule ID should as a value parameter. We can find it in the SonarQube server UI:
我们应该把正确的SonarQube规则ID应该作为一个值参数传递。我们可以在SonarQube服务器用户界面中找到它。
4.2. Deactivating a Rule
4.2.停用一个规则
In the SonarQube out-of-the-box quality profile, we are not able to deactivate any of the predefined rules. Thus, in order to disable the warning on the complete project level, we first need to create a custom quality profile:
在SonarQube开箱即用的质量配置文件中,我们无法停用任何预定义的规则。因此,为了在整个项目层面上禁用警告,我们首先需要创建一个自定义质量配置文件。
In our custom quality profile, we can search for and deactivate any of the predefined Java rules.
在我们的自定义质量配置文件中,我们可以搜索并停用任何预定义的Java规则。
5. Alternative Implementations
5.替代实施方案
Let’s look at some possible alternative ways how to create utilities besides using classes.
让我们看看除了使用类之外,如何创建实用程序的一些可能的替代方法。
5.1. Static Interface Methods
5.1.静态接口方法
Since Java 8, we can define and implement static methods in interfaces:
从Java 8开始,我们可以在接口中定义和实现static方法。
public interface StringUtils {
static boolean isEmpty(String source) {
return source == null || source.length() == 0;
}
static String wrap(String source, String wrapWith) {
return isEmpty(source) ? source : wrapWith + source + wrapWith;
}
}
As we cannot instantiate interfaces, we eliminated the utility class instantiation issue. However, we are creating another problem. Since interfaces are designed to be implemented by other classes, an API user could mistakenly implement this interface.
由于我们不能实例化接口,我们消除了实用类实例化的问题。然而,我们正在制造另一个问题。由于接口被设计成可以由其他类来实现,API用户可能会错误地实现这个接口。
In addition, interfaces cannot contain private constants and static initializers.
此外,接口不能包含私有常量和静态初始化器。
5.2. Static Enum Methods
5.2.静态枚举方法
Enums are containers of managed instances. However, we can create a utility as an enum with zero instances containing only static methods:
枚举是管理实例的容器。然而,我们可以创建一个实用程序作为enum,其实例为零,只包含静态方法。
public enum StringUtils {;
public static boolean isEmpty(String source) {
return source == null || source.length() == 0;
}
public static String wrap(String source, String wrapWith) {
return isEmpty(source) ? source : wrapWith + source + wrapWith;
}
}
As we cannot instantiate enum types, we eliminated the utility class instantiation issue. On the other hand, as the name suggests, enum types are designed for creating actual enumerations, not utility classes.
由于我们不能实例化枚举类型,我们消除了实用类实例化的问题。另一方面,正如其名称所示,枚举类型是为创建实际的枚举而设计的,而不是实用类。
6. Conclusion
6.结论
In this article, we explored utility classes and explained why they shouldn’t have public constructors.
在这篇文章中,我们探讨了实用类并解释了为什么它们不应该有公共构造函数。
In the examples, we covered implementing a private constructor manually and using Lombok annotations. Next, we saw how to suppress and disable the related SonarQube warning. Finally, we looked at two alternative ways to create utilities using interfaces and enums.
在这些例子中,我们涵盖了手动实现一个私有构造函数和使用Lombok注释。接下来,我们看到如何抑制和禁用相关的SonarQube警告。最后,我们看了使用接口和枚举来创建实用程序的两种替代方法。
As always, the source code is available over on GitHub.
像往常一样,源代码可在GitHub上获得。