【问题标题】:Arbitrary allocation of Java.Util.Calendar values任意分配 Java.Util.Calendar 值
【发布时间】:2018-03-14 00:14:45
【问题描述】:

我目前正在努力解决Java.Util.Calendar
我有一种方法可以创建一个日历,其中包含年、月和日的随机值。

public Calendar getRandomGeburtstag() {
        int year = 1999 - (int) (Math.random() * 80);
        int month = (int) (Math.random() * 12) + 1;
        int monthLength = 28;
        int day = (int) (Math.random() * monthLength) + 1;      
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.set(year, month, day);
        return calendar;
} 

我将此日历存储在“Person”类中,该类有一个简单的 getter 方法。
然后我在两个不同的场合要求那个日历。 对象在此期间没有改变。 打印日历会给出两种不同的输出:

java.util.GregorianCalendar[areAllFieldsSet=false,ERA=?,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=?,HOUR=?,HOUR_OF_DAY=?,MINUTE=?,SECOND=?,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]  

java.util.GregorianCalendar[areAllFieldsSet=true,ERA=1,WEEK_OF_YEAR=52,WEEK_OF_MONTH=5,DAY_OF_YEAR=362,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=4,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=3600000,DST_OFFSET=0]

(我省略了保持不变的字段,它们都在两个打印输出中设置)
如您所见,之前未设置的所有字段都在两个请求之间的某处获得了默认值或计算值。 请求之间没有使用反对,因此它可能是 JVM。
问题是,ZONE_OFFSET 字段被我无权访问的方法使用。只有在给出ZONE_OFFSET=? 时才会打印所需的输出。
有什么办法可以停止这些值的自动分配?

【问题讨论】:

  • 我会尝试调试您的代码,同时在 Calendar.internalSet(int, int) 方法中设置断点以查看您的对象在哪里被更改。 JVM 不太可能更改您的对象,因为Calendar 不是单例。一种解决方法是使用不可变类型,但 LocalDate 仅在 8+ 中可用。
  • 对于 Java 6 和 7,LocalDate 可通过ThreeTen Backport 获得。 @KarolDowbecki 我同意推荐它。
  • 所以这发生了一个相当尴尬的转折。我用@KarolDowbecki 的建议调试了代码。事实证明,我只是不知道我的遗留代码实际上做了什么。在某些时候(出于无法理解的原因),它实际上会更改我提供给它的日历。在我认为它会在克隆中进行所有更改之前。给遗留代码一个克隆解决了这个问题。
  • 现在我真的不知道如何处理这个问题。问题本身现在无效(因为所描述的问题首先不存在)但答案仍然有效。 Stackoverflow 告诉我不要删除问题,以便其他人可以使用给定的信息。但我不确定这个问题是否有帮助。
  • 你是对的,你应该把问题留在这里,这样其他读者可能会从答案中受益。很好,你解决了。

标签: java java-7 java.util.calendar


【解决方案1】:

这可能不是您正在寻找的答案,但使用 java.time 包编写会容易得多

public LocalDate getRandomGeburtstag() {
  LocalDate min = LocalDate.of(1919, 1, 1);
  LocalDate max = LocalDate.of(1999, 12, 31);
  long diff = max.toEpochDay() - min.toEpochDay();
  return LocalDate.ofEpochDay(min.toEpochDay() + (long) (Math.random() * diff));
} 

您当前的方法不会产生所有可能的日子,例如29、30、31。我认为这是一种解决方法,但在 epoch millis 上操作时不需要它。

【讨论】:

  • 由于我正在处理的项目尚未转换为 java 8,我很遗憾不能使用java.time(这会让很多事情变得更容易)。大约 2 月 30 日:当前方法将day 的范围限制为 1-28。这个限制显然是用 java 8 解决的。我会更新标签和变量名来澄清。
  • 确实,您可以在 Java 6 和 7 中使用 java.time。将 ThreeTen Backport 添加到您的项目中,并且 (1) 您可以使用相同的功能,并且 (2) 您是面向未来的。如果您的旧代码需要Calendar 对象,请使用DateTimeUtils.toGregorianCalendar(localDateFromAnswer.atStartOfDay(ZoneId.systemDefault()))
【解决方案2】:

也许您可以在这些答案中找到有关Calendar 奇怪行为的更多信息:

基本上,当您调用set 时,所有这些内部字段都不会自动重新计算,并且内部重新计算是任意的,您无法控制何时完成以及重新计算哪些字段。

最好的替代方法是使用 ThreeTen Backport,正如 cmets 中所建议的那样,您可以使用 LocalDate 和 java.time API 的所有其他功能 - 它没有 Calendar 的所有这些巨大的混乱.

但如果你坚持使用旧 API,那么还有一个(丑陋的)替代方案:

SimpleDateFormat sdf = new SimpleDateFormat("d-M-yyyy");
Date date = sdf.parse(day + "-" + month + "-" + year);

Calendar calendar = Calendar.getInstance();
calendar.setTime(date);

Date 将时间字段设置为零,Calendar 将设置所有字段(打印时不设置 ?)。

只是提醒一下,上面的月份值是从 1 到 12,而在 calendar.set 方法中,接受的值是从 0 到 11(这个糟糕的 API 的另一个令人讨厌的行为)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-11
    • 2021-01-10
    • 2016-06-11
    • 1970-01-01
    相关资源
    最近更新 更多