一直在努力寻找实现一个方法的最佳方法,该方法可以生成日历对象的防御副本。
例如:
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是最好的前进方式。
我建议在这里使用"同步块"。