17

一直在尝试找到实现制作日历对象的防御性副本的方法的最佳方法。

例如:

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

在检查空输入并制作副本时,我特别担心线程的交错,或者我是否遗漏了一些非常明显的东西?

4

7 回答 7

28

(我想现在针对的是稍微不同的观众……)

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

clone()是克隆对象的预期方式:这是我期望一个明智的子类挂钩它需要的任何特定于类型的行为的地方。您不需要知道哪些状态位是相关的——您只需让类型自己处理即可。

Calendar.getInstance()比自己使用和设置属性的好处:

  • 您将保留相同的日历类型
  • 您无需担心忘记属性:这是类型的责任
  • 你明确地说出想做什么,并让实现来处理how,这总是很好的。您的代码准确地表达了您的意图。

编辑:就原始问题担心的“线程交错”而言:date参数的值不会改变其他线程所做的任何事情。但是,如果在您获取防御性副本时另一个线程正在改变对象的内容,则容易导致问题。如果这是一个风险,那么基本上你就会遇到更大的问题。

于 2013-01-29T19:41:30.073 回答
18

最简单的方法是:

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

但我强烈建议(尽可能)使用JodaTime在 Java 中表达时间和日期。它具有不可变的类和可变的类。

于 2012-02-10T08:44:53.527 回答
2

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

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

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

Jon Skeet 也提供了一个很好的答案,比我的要好,但我认为这个问题的潜在绊脚石可能会从听到一点点“按合同设计”中受益。虽然该方法已接近尾声,但该方法已帮助我的设计安静了很多。

于 2013-09-27T19:23:13.300 回答
1

只需将您的日历对象包装到 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
    }
}
于 2013-02-04T18:53:05.977 回答
1

下面呢?

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

同步代码中的正确用法取决于您的用例。

于 2015-07-21T15:19:24.910 回答
0

这是无法保证的!

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

日历的防御性复制:如果您不信任获取参考的地方,则不能选择克隆!Calendar 不支持任何采用现有 Calender 对象并创建新对象的构造函数!

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

于 2013-02-05T12:13:20.653 回答
-2

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

于 2013-02-05T12:33:34.350 回答