【问题标题】:Call to method of static java.text.DateFormat not advisable?不建议调用静态 java.text.DateFormat 方法?
【发布时间】:2026-01-26 03:40:01
【问题描述】:

我收到 Find Bugs 错误 - 调用静态 java.text.DateFormat 方法和 我不知道为什么在下面做这些事情不好/不建议这样做。

private static final Date TODAY = Calendar.getInstance().getTime();
private static final DateFormat yymmdd = new SimpleDateFormat("yyMMdd"); 

private String fileName = "file_" + yymmdd.format(TODAY);

【问题讨论】:

  • 调用方法不谈,这段代码看起来确实有点可疑 - TODAY 将是一个恒定的一天,格式化程序是最终的非静态的?
  • 此外,TODAY 可能并不总是“今天”,例如,如果加载了此类,然后 JVM 一直运行到第二天 - 任何依赖 TODAY 为当天的逻辑都不会工作,除非您考虑到这种差异。
  • @Peter - 是的,每次运行都会重置程序。

标签: java static


【解决方案1】:

尚未提及的替代方法是使用 ThreadLocal。请参阅http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html 了解更多信息 + 3 个选项之间的性能比较:

  • 每次都创建一个实例
  • 同步访问
  • 使用 ThreadLocal

使用 ThreadLocal 的示例:

private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyMMdd");
    }
};

用法:

DATE_FORMAT.get().format( TODAY )

【讨论】:

    【解决方案2】:

    DateFormat 不是线程安全的,这意味着它们维护状态的内部表示。如果多个线程同时访问同一个实例,在静态上下文中使用它们会产生一些非常奇怪的错误。

    我的建议是让你的变量本地化到你使用它们的地方,而不是让它们成为类的静态属性。看起来你在初始化类时可能会这样做,所以你可以在构造函数中这样做:

    public class MyClass {
        private String fileName;
    
        public MyClass() {
            final Date today = Calendar.getInstance().getTime();
            final DateFormat yymmdd = new SimpleDateFormat("yyMMdd"); 
    
            this.fileName = "file_" + yymmdd.format(TODAY);
        }
        ...
    }
    

    如果您需要在多个地方使用格式化程序,您可能只需创建模式static final 并在需要时创建一个新的本地DateFormat

    public class MyClass {
        private static final String FILENAME_DATE_PATTERN = "yyMMdd";
    
        public void myMethod() {
            final DateFormat format = new SimpleDateFormat(FILENAME_DATE_PATTERN);
            // do some formatting
        }
    }
    

    问题的FindBugs documentation 说:

    正如 JavaDoc 所述,DateFormats 是 多线程本质上是不安全的 采用。检测器找到了一个调用 DateFormat 的一个实例,它具有 通过静态场获得。这 看起来很可疑。

    有关这方面的更多信息,请参阅 Sun 错误 #6231579 和 Sun 错误 #6178997。

    javadoc for DateFormat 建议:

    日期格式不同步。它 建议单独创建 每个线程的格式实例。如果 多个线程访问一个格式 同时,它必须同步 外部。

    Jack Leow's answer 对静态使用“TODAY”的语义也有很好的看法。

    顺便说一句,我实际上已经在高流量的生产环境中看到了这种情况,而且一开始调试是一件非常令人困惑的事情;所以根据我的经验,FindBugs 警告实际上是一个有用的建议(不像其他一些静态分析规则,有时看起来很挑剔)。

    【讨论】:

    • 我不太确定,我认为每次调用 myMethod() 时创建一个 SimpleDateFormat 太昂贵了。
    • @eradicus - 你有性能指标证明它“太贵了”吗?除非您发现它是应用程序的瓶颈,否则我更喜欢本地方法,而不是使用 static finals 使全局范围混乱。强制性 Knuth:c2.com/cgi/wiki?PrematureOptimization
    • 为什么变量是“final”(即常量)还不够?如果它不能改变所有线程应该能够共享它,不是吗?还是初始化的竞争条件问题?
    • final 仅表示无法重新分配 reference - 无法保证引用指向的对象的内容 final
    【解决方案3】:

    Commons Lang 有一个线程安全的FastDateFormat 对象。 它只是格式化,而不是解析。

    如果您可以使用 commons-lang,这可能对您很有效。

    private static final Date TODAY = Calendar.getInstance().getTime();
    private static final FastDateFormat yymmdd = FastDateFormat.getInstance("yyMMdd");
    
    private String fileName = "file_" + yymmdd.format(TODAY);
    

    【讨论】:

      【解决方案4】:

      我不确定 FindBugs 是否在抱怨这一点,但我在您的代码中看到的一个问题是您将 TODAY 定义为类级别(静态)、常量(最终)变量。这传达了您希望 TODAY 永不改变的意图(我不认为是这种情况,因为 java.util.Dates 是可变的,但这是另一回事)。

      想想如果您的应用程序运行多天会发生什么? TODAY(除非您更新它)将引用应用程序启动的日期,而不是当前日期。你确定这是你的意思吗?

      这可能根本不是您代码中的错误,但意图尚不清楚,我相信可能是 FindBugs 所抱怨的。

      【讨论】:

        【解决方案5】:

        您可以通过将所有对 DateFormat 的引用包装在同步块中来消除这种情况 - 只需确保所有调用都包装在 same 同步对象中!

        【讨论】:

        • 这可能会消除 FindBugs 错误,但是在需要时只新建一个本地 DateFormat 不是更合乎逻辑吗?
        • @Rob,我想这取决于同步是否真的可能偶尔阻塞,或者这只是安抚 FindBugs 的一种形式。
        • 是的。我想如果创建新 DateFormat 的开销会以某种方式影响系统性能,那么同步可能是一种替代方法,但我怀疑在大多数情况下它会。另外,如果是这样的话,它可能是一个更高负载的系统,我认为同步会以类似的方式阻碍性能。
        【解决方案6】:

        我认为这是因为格式不是线程安全的?

        (我还没有看到 findbugs 抱怨什么,你能提供 警告文字?)

        【讨论】:

          【解决方案7】:

          你确定不是

          private static final DateFormat yymmdd = new SimpleDateFormat("yyMMdd"); 
          

          ?这就是错误消息的含义。

          我认为它的目的是DateFormat 不是线程安全的,因此将实例作为静态字段表明潜在的竞争条件。

          【讨论】:

            【解决方案8】:

            一方面它不是线程安全的。

            【讨论】: