日历的防御副本



一直在努力寻找实现一个方法的最佳方法,该方法可以生成日历对象的防御副本。

例如:

public void setDate(Calendar date) {
    // What to do.... 
}

当检查空输入并进行复制时,我特别担心线程的交错,或者我错过了一些非常明显的东西?

(我想现在的目标受众略有不同…)

如果我绝对必须使用Calendar(而不是Joda Time),我会使用clone()。你在评论中辩称,你担心一个"顽皮的子类"——你会建议如何在任何方案中解决这个问题?如果你对所涉及的子类一无所知,也不信任它们,那么你就无法保存特定类型的数据。如果你不相信子类不会把事情搞砸,那么你通常会遇到更大的问题。在执行日期/时间计算时,您如何相信它能给您正确的结果?

clone()所期望的克隆对象的方式:我希望可感知的子类能够钩住它所需要的任何类型特定的行为。你不需要知道哪些状态是相关的——你只需要让类型自己处理它。

与使用Calendar.getInstance()和自己设置属性相比的优势:

  • 您将保留相同的日历类型
  • 您不必担心忘记属性:这是类型的责任
  • 你明确地告诉你想做什么,并让实现来处理如何的问题,这总是很好的。您的代码准确地表达了您的意图

编辑:就最初的问题所担心的"线程交错"而言:无论其他线程做什么,date参数的值都不会改变。但是,如果在你进行防御复制时,另一个线程正在改变对象的内容,很容易引起问题。如果这是一种风险,那么基本上你会遇到更大的问题。

最简单的方法是:

copy = Calendar.getInstance(original.getTimeZone());
copy.setTime(original.getTime());

但我强烈建议(只要可能)使用JodaTime来用Java表示时间和日期。它既有不可变的类,也有可变的类。

我知道这是旧的,但我想我应该投入我的两分钱。

如果您按照约定进行编程,则一个对象不对另一个对象的错误负责。Calendar实现了Cloneable,这意味着子类也实现了!如果Calendar的子类破坏了Cloneable约定,则需要更正的是子类,而不是调用clone的类。

在OO编程中,对象应该只关心类&它所涉及的合同。当你问"如果一个子类破坏了它怎么办?"每当一个对象将一个对象作为参数时,总是有可能该对象是一个子类并破坏了所有东西,这会使设计变得非常复杂。当您调用getX()时,您是否进行了防御性编程,以确保它不会为子类抛出ArithmeticException异常?

Jon Skeet也提供了一个很好的答案,比我的答案更好,但我认为,一个潜在的偶然发现这个问题的人可能会从听一点"合同设计"中受益。虽然这种方法已经接近尾声,但这种方法帮助我的设计平静了很多。

只需将Calendar对象包装到ThreadLocal中。这将保证Calendar的每个实例仅由一个线程使用。类似这样的东西:

public class ThreadLocalCalendar
{
    private final static ThreadLocal <Calendar> CALENDAR =
        new ThreadLocal <Calendar> ()
        {
            @Override
            protected Calendar initialValue()
            {
                GregorianCalendar calendar = new GregorianCalendar();
                // Configure calendar here.  Set time zone etc.
                return calendar;
            }
        };
    // Called from multiple threads in parallel
    public void foo ()
    {
        Calendar calendar = CALENDAR.get ();
        calendar.setTime (new Date ());
        // Use calendar here safely, because it belongs to current thread
    }
}

以下内容如何?

public synchronized void setDate(Calendar date) {
    // What to do.... 
    Calendar anotherCalendar = Calendar.getInstance();
    anotherCalendar.setTimeInMillis(date.getTimeInMillis());
}

synchronized在代码中的正确用法取决于您的用例。

这是无法保证的!

线程安全:除非你知道你从哪里获得参考的一方实施的安全方案,否则无法确保。该方本可以给你一个新的引用,在这种情况下,你可以简单地按原样使用该引用。该方可能已经发布了关于该日历引用的安全方案,在这种情形下,你也可以使用相同的方案(有时不可能)来检查非空引用,键入并使用getInstance()进行防御复制。我认为,如果不了解这些,就不可能确保线程安全。

防御性复制日历:如果你不信任你获得参考的地方,克隆就不是一种选择!日历不支持任何采用现有日历对象并创建新对象的构造函数!

简而言之,没有办法解决你的问题。JodaTime是最好的前进方式。

我建议在这里使用"同步块"。

最新更新