Java最佳实践:仅包含静态方法的类

59

我有一个应用程序,其中有一个名为PlausibilityChecker的类。这个类只有静态方法,比如checkZipcodeFormatcheckMailFormat。我在GUI类中使用它们来在将输入发送到下一层之前检查输入。

这是一种好的做法吗?我认为仅使用静态方法可以使我不必担心向GUI类传递实例或在每个GUI类中具有不引用GUI对象的实例字段。

我注意到Java NIOFiles类只有静态方法,所以我认为这不可能是那么糟糕的。


3
只要它没有状态,那就没问题。 - Sotirios Delimanolis
7
静态类是可以的,只要别忘了添加私有构造函数。噢,还要注意多线程访问。 - m0skit0
请提供有关您的程序结构的更多信息。 - Maroun
我们通常将这样的类命名为*Helper,这样做没有任何问题。额外的private constructor()可以防止实例化。 - deathangel908
我经常看到它被称为“实用工具”。 - DoubleDouble
请参见https://dev59.com/Q3RB5IYBdhLWcg3wxZ7Y。 - Andy Thomas
6个回答

60

我认为你的做法是正确的。此外,以下是一些关于你的实用类的建议:

  • 确保它没有任何状态。这意味着类中没有字段,除非声明为static final。同时,确保这个字段也是不可变的,例如String
  • 确保它不能成为其他类的超类。将类声明为final,这样其他程序员就无法扩展它。
  • 这点有争议,但你可以声明一个无参构造函数private,这样其他类就不能创建你的实用类的实例(使用反射或类似方法可能会成功,但没有必要对类进行过度保护)。为什么你不应该这样做?嗯,这是一种奇怪的情况,即你想/需要通过接口注入实用类的实例,而不是直接在你的类中使用它。这篇文章提供了一个例子。这种设计确实很奇怪,但可能会发生(如上面链接所示),但如果你不会遇到这种情况,最好的建议是将构造函数设置为private

有很多库提供实用类,以帮助我们程序员完成工作。其中最著名的之一是Apache Common库集。它是开源的,你可以查看代码以了解如何设计这些实用类来创建自己的实用类。(免责声明:我不工作或支持这些库,我是它们的快乐用户)

重要提示:避免使用单例模式来创建实用类


他使用单例模式并正常创建所有函数,除了 getInstance(),这样不是更好吗? - Bugs Happen
@BugsHappen,不要使用单例模式,因为它是“邪恶”的(http://c2.com/cgi/wiki?SingletonsAreEvil)。 - Luiggi Mendoza
1
对于第一个要点,我认为仅仅说确保任何字段都是静态的最终字段还不够,因为最终字段可以引用可变的内容;我会说确保没有任何可变的内容,并且没有依赖于任何外部内容(文件系统、数据库等)。尽管nio Files示例违反了最后一部分。 - Nathan Hughes

29

在Java 8中,你现在可以将静态工具类改为带有静态实现的接口。这消除了使类成为final并提供私有构造函数的必要性。只需将“class”更改为“interface”,如果有的话,则删除“final”单词(所有接口都是抽象的,因此它们不能是最终的)。由于接口方法始终是公共的,因此您可以从它们中删除任何公共作用域。如果有私有构造函数,那么也要将其删除(无法使用构造函数编译接口,因为它们无法实例化)。代码量更少,看起来更加简洁。不需要重构已经使用它的任何类。


扩展方法在语言级别“7”不受支持。 静态接口方法需要API级别24。 - Vahag Chakhoyan

6
不必担心子类化或实例化。JDK中以下的实用程序类可以进行子类化或实例化,但在所有这些年中没有人滥用它们。人们并不是那么愚蠢。
java.beans.Beans
java.beans.PropertyEditorManager
java.lang.invoke.LambdaMetafactory
java.lang.reflect.Modifier
java.net.URLDecoder                                   ...but not URLEncoder:)
javax.management.DefaultLoaderRepository
javax.management.Query
javax.management.loading.DefaultLoaderRepository
javax.management.relation.RoleStatus
javax.print.ServiceUI
javax.swing.UIManager
javax.swing.plaf.basic.BasicBorders
javax.swing.plaf.basic.BasicGraphicsUtils
javax.swing.plaf.basic.BasicHTML
javax.swing.plaf.basic.BasicIconFactory
javax.swing.plaf.metal.MetalBorders
javax.swing.plaf.metal.MetalIconFactory
javax.swing.text.Utilities
javax.swing.text.html.HTML

作为公有API,您确实希望抑制默认构造函数,否则在javadoc页面上会出现一个令人尴尬和困惑的未记录的构造函数。对于您自己的内部API,这无关紧要,谁也不在乎。
然而,没有理由禁止子类化。如果有人想为了任何原因而对实用程序类进行子类化,则应该允许他们这样做。当然,私有构造函数将作为副作用抑制子类化。
在Java8中,有更多的设计可能性需要考虑 - 一个只包含静态方法的接口 - 这和一个只包含静态方法的类一样好。接口和类都不是为此目的而设计的,所以任何一个都可以。但是,请不要期望在接口的子类型中继承这些静态方法 - 接口静态方法不可继承。使用接口的一个加分点是我们不需要在javadoc中抑制默认构造函数的出现。 一个只包含默认方法的接口 - 通过继承访问。这很有趣,但通常存在问题(继承仅在非静态上下文中工作)。但在某些API设计中,例如这个html builder API,它可能是更好的选择。

这与Joshua Bloch在他的《Effective Java》一书中的建议相反,他建议您只使用接口来定义类型。 - Ogen

2

在Java中,只有静态方法的类是实用方法的常见模式。标准库中的示例包括FilesCollectionsExecutors

对于这种实用程序类,最好确保您的类不能被实例化,以清楚地表明类的意图。您可以通过显式声明私有构造函数来实现这一点。请参阅Josh Bloch的《Effective Java》中的第4条:使用私有构造函数强制执行不可实例化了解详细信息。


1

如果你只是提供“工具”方法给其他类使用,这是很好的做法。


1

单例模式在特定情况下是可行的。

为了测试目的,您可能希望模拟对单例的引用,以避免在单元测试期间产生副作用。在这种情况下,使用依赖注入框架(如Spring)处理实例的创建并使用常规方法而不是静态方法,这些方法可以被覆盖,可能是有意义的。

如果仍计划使用静态方法,请确保在多线程环境中使用时调用是线程安全的。(例如双重检查锁定)。


2
静态方法!=单例模式。对于仅具有静态方法的实用程序类,通常根本没有类的实例。 - markusk

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