何时不在Java中使用静态关键字?

When NOT to use the static keyword in Java?

在爪哇使用静态关键字在方法签名上被认为是什么差的实践?如果一个方法基于一些参数执行一个函数,并且不需要访问非静态的字段,那么您不总是希望这些类型的方法是静态的吗?


在大规模Java应用程序中,您将遇到的两大弊病是:

  • 静态方法,纯函数除外*
  • 可变静态字段

这些破坏了代码的模块性、可扩展性和可测试性,在某种程度上,我意识到我不可能希望在这个有限的时间和空间内说服您这样做。

*"纯函数"是不修改任何状态的任何方法,其结果只依赖于提供给它的参数。例如,任何执行I/O(直接或间接)的函数都不是纯函数,但是math.sqrt()当然是。

更多关于纯函数(self-link)的废话,以及为什么你要坚持它们。

我强烈建议您支持编程的"依赖注入"风格,可能由Spring或Guice等框架支持(免责声明:我是后者的合著者)。如果您这样做是正确的,那么基本上就不需要可变静态或非纯静态方法。


您不希望它是静态的一个原因是允许它在子类中被重写。换句话说,行为可能不取决于对象内的数据,而是取决于对象的确切类型。例如,您可能有一个常规的集合类型,它具有一个isReadOnly属性,该属性在始终可变的集合中返回false,在始终不变的集合中返回true,并且依赖于其他集合中的实例变量。

然而,在我的经验中,这是非常罕见的——通常应该明确说明。通常我会创建一个不依赖于任何对象状态静态的方法。


一般来说,我更喜欢实例方法,原因如下:

  • 静态方法使测试变得困难,因为它们不能被替换,
  • 静态方法更面向过程。
  • 在我看来,静态方法对于实用程序类(如StringUtils)是可以的,但我更喜欢尽量避免使用它们。


    您所说的有点正确,但是当您想要重写派生类中该方法的行为时会发生什么?如果是静态的,就不能这样做。

    例如,考虑以下DAO类型类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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就是一个很好的例子,它通过环境对象而不是静态调用来封装状态。


    这里有两个问题1)创建对象的静态方法在第一次访问时保持在内存中加载?这不是一个缺点吗?2)使用Java的优点之一是它的垃圾收集功能-我们在使用静态方法时忽略了这一点吗?


    静态方法的另一个烦恼是:如果不在函数周围创建包装类,就无法轻松地传递对该函数的引用。例如-类似于:

    1
    FunctorInterface f = new FunctorInterface() { public int calc( int x) { return MyClass.calc( x); } };

    我讨厌这种Java制作的工作。也许Java的后期版本会得到委托或类似的函数指针/过程类型机制?

    一个小问题,但还有一件事是不喜欢无缘无故的静态函数,er,方法。


    如果要独立于类的任何对象使用类成员,则应将其声明为静态的。如果声明为静态,则可以在没有类对象的现有实例的情况下访问它。静态成员由该特定类的所有对象共享。


    这是正确的。事实上,你必须扭曲否则可能是一个合理的设计(有一些不相关的功能与一个类)到Java术语。这就是为什么你看到了捕获所有类,比如fredswingutils和yetanotherioutils。