什么时候在方法签名上使用 Java 中的 static 关键字被认为是不好的做法?如果一个方法基于一些参数执行一个函数,并且不需要访问非静态字段,那么您不总是希望这些类型的方法是静态的吗?
9 回答
在大型 Java 应用程序中您将遇到的两个最大的弊端是
- 静态方法,纯函数除外*
- 可变静态字段
这些在一定程度上破坏了代码的模块化、可扩展性和可测试性,我意识到我不可能希望在有限的时间和空间内说服你。
*“纯函数”是不修改任何状态并且其结果仅取决于提供给它的参数的任何方法。因此,例如,任何执行 I/O(直接或间接)的函数都不是纯函数,但 Math.sqrt() 当然是。
关于纯函数(自链接)以及为什么要坚持使用它们的更多废话。
我强烈建议您支持“依赖注入”编程风格,可能由 Spring 或 Guice 等框架支持(免责声明:我是后者的合著者)。如果你做对了,你基本上永远不需要可变静态或非纯静态方法。
您可能不希望它是静态的一个原因是允许它在子类中被覆盖。换句话说,行为可能不取决于对象内的数据,而是取决于对象的确切类型。例如,您可能有一个通用集合类型,其isReadOnly
属性将false
在始终可变集合、true
始终不可变集合中返回,并依赖于其他集合中的实例变量。
然而,这在我的经验中是非常罕见的——为了清楚起见,通常应该明确指定。通常我会制作一个不依赖于任何静态对象状态的方法。
一般来说,我更喜欢实例方法,原因如下:
- 静态方法使测试变得困难,因为它们不能被替换,
- 静态方法更面向过程。
在我看来,静态方法适用于实用程序类(如StringUtils
),但我更愿意尽可能避免使用它们。
你说的是真的,但是当你想在派生类中重写该方法的行为时会发生什么?如果它是静态的,则不能这样做。
例如,考虑以下 DAO 类型类:
class CustomerDAO {
public void CreateCustomer( Connection dbConn, Customer c ) {
// Some implementation, created a prepared statement, inserts the customer record.
}
public Customer GetCustomerByID( Connection dbConn, int customerId ) {
// Implementation
}
}
现在,这些方法都不需要任何“状态”。他们需要的一切都作为参数传递。所以它们很容易是静态的。现在要求您需要支持不同的数据库(比如说 Oracle)
由于这些方法不是静态的,您可以创建一个新的 DAO 类:
class OracleCustomerDAO : CustomerDAO {
public void CreateCustomer( Connection dbConn, Customer c ) {
// Oracle specific implementation here.
}
public Customer GetCustomerByID( Connection dbConn, int customerId ) {
// Oracle specific implementation here.
}
}
这个新类现在可以用来代替旧类。如果您使用依赖注入,它甚至可能根本不需要更改代码。
但是如果我们将这些方法设为静态,那会使事情变得更加复杂,因为我们不能简单地覆盖新类中的静态方法。
静态方法通常出于两个目的而编写。第一个目的是拥有某种全局实用方法,类似于java.util.Collections 中的那种功能。这些静态方法通常是无害的。第二个目的是通过单例和工厂等各种设计模式来控制对象实例化并限制对资源(如数据库连接)的访问。如果实施不当,这些可能会导致问题。
对我来说,使用静态方法有两个缺点:
- 它们使代码的模块化程度降低,并且更难测试/扩展。大多数答案已经解决了这个问题,所以我不再赘述。
- 静态方法往往会导致某种形式的全局状态,这通常是潜在错误的原因。这可能发生在为上述第二个目的而编写的编写不佳的代码中。让我详细说明。
例如,考虑一个需要将某些事件记录到数据库的项目,并且还依赖于数据库连接来获取其他状态。假设正常情况下,首先初始化数据库连接,然后配置日志框架,将某些日志事件写入数据库。现在假设开发人员决定从手写数据库框架迁移到现有的数据库框架,例如 hibernate。
但是,这个框架很可能有自己的日志配置——如果它碰巧使用与你相同的日志框架,那么配置之间很可能会出现各种冲突。突然间,切换到不同的数据库框架会导致系统不同部分出现看似无关的错误和故障。发生此类故障的原因是日志配置维护通过静态方法和变量访问的全局状态,并且系统的不同部分可以覆盖各种配置属性。
为了避免这些问题,开发人员应该避免通过静态方法和变量存储任何状态。相反,他们应该构建干净的 API,让用户根据需要管理和隔离状态。BerkeleyDB就是一个很好的例子,它通过环境对象而不是静态调用来封装状态。
这是正确的。实际上,您必须将原本可能是合理的设计(使某些函数与类不关联)扭曲为 Java 术语。这就是为什么您会看到诸如 FredsSwingUtils 和 YetAnotherIOUtils 之类的包罗万象的类。
当您想独立于该类的任何对象使用类成员时,应将其声明为静态的。
如果它被声明为静态,则可以在没有该类对象的现有实例的情况下访问它。静态成员由该特定类的所有对象共享。
关于静态方法的另一个烦恼:如果不围绕它创建一个包装类,就没有简单的方法来传递对这样一个函数的引用。例如 - 类似:
FunctorInterface f = new FunctorInterface() { public int calc( int x) { return MyClass.calc( x); } };
我讨厌这种 java make-work。也许更高版本的 java 将获得委托或类似的函数指针/过程类型机制?
一个小小的抱怨,但还有一点不喜欢免费的静态函数,呃,方法。
这里有两个问题 1)创建对象的静态方法在第一次访问时保持加载在内存中?这(仍然加载在内存中)不是一个缺点吗?2) 使用 Java 的优点之一是它的垃圾收集特性——当我们使用静态方法时,我们是否忽略了这一点?