Java 编程问题:三、使用日期和时间4

简介: Java 编程问题:三、使用日期和时间

Date-Instant


为了从Date转换到Instant,可采用Date.toInstant()方法求解。可通过Date.from(Instant instant)方法实现反转:


  • DateInstant可以这样完成:


Date date = new Date();
// e.g., 2019-02-27T12:02:49.369Z, UTC
Instant instantFromDate = date.toInstant();


   Instant到Date可以这样完成:

Instant instant = Instant.now();
// Wed Feb 27 14:02:49 EET 2019, default system time zone
Date dateFromInstant = Date.from(instant);



请记住,Date不是时区感知的,但它显示在系统默认时区中(例如,通过toString())。Instant是 UTC 时区。

让我们快速地将这些代码片段包装在两个工具方法中,它们在一个工具类DateConverters中定义:

public static Instant dateToInstant(Date date) {
  return date.toInstant();
}
public static Date instantToDate(Instant instant) {
  return Date.from(instant);
}


此外,让我们使用以下屏幕截图中的方法来丰富此类:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VC4DDDjb-1657077517352)(img/ae07c212-3c31-4d8f-91b1-b494d5b9d393.png)]


屏幕截图中的常量DEFAULT_TIME_ZONE是系统默认时区:

public static final ZoneId DEFAULT_TIME_ZONE = ZoneId.systemDefault();



DateLocalDate


Date对象可以通过Instant对象转换为LocalDate。一旦我们从给定的Date对象中获得Instant对象,解决方案就可以应用于它系统默认时区,并调用toLocaleDate()方法:

// e.g., 2019-03-01
public static LocalDate dateToLocalDate(Date date) {
  return dateToInstant(date).atZone(DEFAULT_TIME_ZONE).toLocalDate();
}


LocalDateDate的转换应该考虑到LocalDate不包含Date这样的时间成分,所以解决方案必须提供一个时间成分作为一天的开始(关于这个问题的更多细节可以在“一天的开始和结束”问题中找到):

// e.g., Fri Mar 01 00:00:00 EET 2019
public static Date localDateToDate(LocalDate localDate) {
  return Date.from(localDate.atStartOfDay(
    DEFAULT_TIME_ZONE).toInstant());
}



DateLocalDateTime

DateDateLocalTime的转换与从DateLocalDate的转换是一样的,只是溶液应该调用toLocalDateTime()方法如下:

// e.g., 2019-03-01T07:25:25.624
public static LocalDateTime dateToLocalDateTime(Date date) {
  return dateToInstant(date).atZone(
    DEFAULT_TIME_ZONE).toLocalDateTime();
}


LocalDateTimeDate的转换非常简单。只需应用系统默认时区并调用toInstant()

// e.g., Fri Mar 01 07:25:25 EET 2019
public static Date localDateTimeToDate(LocalDateTime localDateTime) {
  return Date.from(localDateTime.atZone(
    DEFAULT_TIME_ZONE).toInstant());
}




DateZonedDateTime

DateZonedDateTime的转换可以通过从给定Date对象获取Instant对象和系统默认时区来完成:

// e.g., 2019-03-01T07:25:25.624+02:00[Europe/Athens]
public static ZonedDateTime dateToZonedDateTime(Date date) {
  return dateToInstant(date).atZone(DEFAULT_TIME_ZONE);
}


将ZonedDateTime转换为Date就是将ZonedDateTime转换为Instant:

// e.g., Fri Mar 01 07:25:25 EET 2019
public static Date zonedDateTimeToDate(ZonedDateTime zonedDateTime) {
  return Date.from(zonedDateTime.toInstant());
}



DateOffsetDateTime

DateOffsetDateTime的转换依赖于toOffsetDateTime()方法:

// e.g., 2019-03-01T07:25:25.624+02:00
public static OffsetDateTime dateToOffsetDateTime(Date date) {
  return dateToInstant(date).atZone(
    DEFAULT_TIME_ZONE).toOffsetDateTime();
}


OffsetDateTimeDate的转换方法需要两个步骤。首先将OffsetDateTime转换为LocalDateTime;其次将LocalDateTime转换为Instant,对应偏移量:

// e.g., Fri Mar 01 07:55:49 EET 2019
public static Date offsetDateTimeToDate(
    OffsetDateTime offsetDateTime) {
  return Date.from(offsetDateTime.toLocalDateTime()
    .toInstant(ZoneOffset.of(offsetDateTime.getOffset().getId())));
}



DateLocalTime


Date转换为LocalTime可以依赖LocalTime.toInstant()方法,如下所示:


// e.g., 08:03:20.336
public static LocalTime dateToLocalTime(Date date) {
  return LocalTime.ofInstant(dateToInstant(date), DEFAULT_TIME_ZONE);
}


LocalTime转换为Date应该考虑到LocalTime没有日期组件。这意味着解决方案应将日期设置为 1970 年 1 月 1 日,即纪元:

// e.g., Thu Jan 01 08:03:20 EET 1970
public static Date localTimeToDate(LocalTime localTime) {
  return Date.from(localTime.atDate(LocalDate.EPOCH)
    .toInstant(DEFAULT_TIME_ZONE.getRules()
      .getOffset(Instant.now())));
}



Date-OffsetTime

Date转换为OffsetTime可以依赖OffsetTime.toInstant()方法,如下所示:

// e.g., 08:03:20.336+02:00
public static OffsetTime dateToOffsetTime(Date date) {
  return OffsetTime.ofInstant(dateToInstant(date), DEFAULT_TIME_ZONE);
}

OffsetTime转换为Date应该考虑到OffsetTime没有日期组件。这意味着解决方案应将日期设置为 1970 年 1 月 1 日,即纪元:

// e.g., Thu Jan 01 08:03:20 EET 1970
public static Date offsetTimeToDate(OffsetTime offsetTime) {
  return Date.from(offsetTime.atDate(LocalDate.EPOCH).toInstant());
}




73 迭代一系列日期


假设范围是由开始日期 2019 年 2 月 1 日和结束日期 2019 年 2 月 21 日界定的。这个问题的解决方案应该循环【2019 年 2 月 1 日,2019 年 2 月 21 日】间隔一天,并在屏幕上打印每个日期。基本上要解决两个主要问题:


  • 一旦开始日期和结束日期相等,就停止循环。
  • 每天增加开始日期直到结束日期。




JDK8 之前

在 JDK8 之前,解决方案可以依赖于Calendar工具类。绑定到本书的代码包含此解决方案。



从 JDK8 开始

首先,从 JDK8 开始,可以很容易地将日期定义为LocalDate,而不需要Calendar的帮助:


LocalDate startLocalDate = LocalDate.of(2019, 2, 1);
LocalDate endLocalDate = LocalDate.of(2019, 2, 21);


一旦开始日期和结束日期相等,我们就通过LocalDate.isBefore(ChronoLocalDate other)方法停止循环。此标志方法检查此日期是否早于给定日期。

使用LocalDate.plusDays(long daysToAdd)方法逐日增加开始日期直到结束日期。在for循环中使用这两种方法会产生以下代码:


for (LocalDate date = startLocalDate; 
       date.isBefore(endLocalDate); date = date.plusDays(1)) {
  // do something with this day
  System.out.println(date);
}


输出的快照应如下所示:

2019-02-01
2019-02-02
2019-02-03
...
2019-02-20



从 JDK9 开始


JDK9 可以用一行代码解决这个问题。由于新的LocalDate.datesUntil(LocalDate endExclusive)方法,这是可能的。此方法返回Stream<LocalDate>,增量步长为一天:

startLocalDate.datesUntil(endLocalDate).forEach(System.out::println);


如果增量步骤应以天、周、月或年表示,则依赖于LocalDate.datesUntil(LocalDate endExclusive, Period step)。例如,1 周的增量步骤可以指定如下:

startLocalDate.datesUntil(endLocalDate, Period.ofWeeks(1)).forEach(System.out::println);


输出应为(第 1-8 周,第 8-15 周),如下所示:

2019-02-01
2019-02-08
2019-02-15



74 计算年龄

可能最常用的两个日期之间的差异是关于计算一个人的年龄。通常,一个人的年龄以年表示,但有时应提供月,甚至天。



JDK8 之前

在 JDK8 之前,试图提供一个好的解决方案可以依赖于Calendar和/或SimpleDateFormat。绑定到本书的代码包含这样一个解决方案。



从 JDK8 开始

更好的方法是升级到 JDK8,并依赖以下简单的代码片段:

LocalDate startLocalDate = LocalDate.of(1977, 11, 2);
LocalDate endLocalDate = LocalDate.now();
long years = ChronoUnit.YEARS.between(startLocalDate, endLocalDate);


由于Period类的原因,将月和日添加到结果中也很容易实现:

Period periodBetween = Period.between(startLocalDate, endLocalDate);


现在,可以通过periodBetween.getYears()、periodBetween.getMonths()、periodBetween.getDays()获得以年、月、日为单位的年龄。

例如,在当前日期 2019 年 2 月 28 日和 1977 年 11 月 2 日之间,我们有 41 年 3 个月 26 天。



75 一天的开始和结束


在 JDK8 中,可以通过几种方法来找到一天的开始/结束。

让我们考虑一下通过LocalDate表达的一天:

LocalDate localDate = LocalDate.of(2019, 2, 28);


找到 2019 年 2 月 28 日一天的开始的解决方案依赖于一个名为atStartOfDay()的方法。此方法从该日期午夜 00:00 返回LocalDateTime:

// 2019-02-28T00:00
LocalDateTime ldDayStart = localDate.atStartOfDay();


或者,该溶液可以使用of(LocalDate date, LocalTime time)方法。该方法将给定的日期和时间组合成LocalDateTime。因此,如果经过的时间是LocalTime.MIN(一天开始时的午夜时间),则结果如下:

// 2019-02-28T00:00
LocalDateTime ldDayStart = LocalDateTime.of(localDate, LocalTime.MIN);



一个LocalDate物体的一天结束时间至少可以用两种方法得到。一种解决方案是依靠LocalDate.atTime(LocalTime time)。得到的LocalDateTime可以表示该日期与一天结束时的组合,如果解决方案作为参数传递,LocalTime.MAX(一天结束时午夜前的时间):

// 2019-02-28T23:59:59.999999999
LocalDateTime ldDayEnd = localDate.atTime(LocalTime.MAX);


或者,该解决方案可以通过atDate(LocalDate date)方法将LocalTime.MAX与给定日期结合:

// 2019-02-28T23:59:59.999999999
LocalDateTime ldDayEnd = LocalTime.MAX.atDate(localDate);


由于LocalDate没有时区的概念,前面的例子容易出现由不同的角落情况引起的问题,例如夏令时。有些夏令时会在午夜(00:00 变为 01:00 AM)更改时间,这意味着一天的开始时间是 01:00:00,而不是 00:00:00。为了缓解这些问题,请考虑以下示例,这些示例将前面的示例扩展为使用夏令时感知的ZonedDateTime:


// 2019-02-28T00:00+08:00[Australia/Perth]
ZonedDateTime ldDayStartZone 
  = localDate.atStartOfDay(ZoneId.of("Australia/Perth"));
// 2019-02-28T00:00+08:00[Australia/Perth]
ZonedDateTime ldDayStartZone = LocalDateTime
  .of(localDate, LocalTime.MIN).atZone(ZoneId.of("Australia/Perth"));
// 2019-02-28T23:59:59.999999999+08:00[Australia/Perth]
ZonedDateTime ldDayEndZone = localDate.atTime(LocalTime.MAX)
  .atZone(ZoneId.of("Australia/Perth"));
// 2019-02-28T23:59:59.999999999+08:00[Australia/Perth]
ZonedDateTime ldDayEndZone = LocalTime.MAX.atDate(localDate)
  .atZone(ZoneId.of("Australia/Perth"));


现在,我们来考虑一下-LocalDateTime,2019 年 2 月 28 日,18:00:00:

LocalDateTime localDateTime = LocalDateTime.of(2019, 2, 28, 18, 0, 0);


显而易见的解决方案是从LocalDateTime中提取LocalDate,并应用前面的方法。另一个解决方案依赖于这样一个事实,Temporal接口的每个实现(包括LocalDate)都可以利用with(TemporalField field, long newValue)方法。主要是,with()方法返回这个日期的一个副本,其中指定的字段ChronoField设置为newValue。因此,如果解决方案将ChronoField.NANO_OF_DAY(一天的纳秒)设置为LocalTime.MIN,那么结果将是一天的开始。这里的技巧是通过toNanoOfDay()将LocalTime.MIN转换为纳秒,如下所示:

// 2019-02-28T00:00
LocalDateTime ldtDayStart = localDateTime
  .with(ChronoField.NANO_OF_DAY, LocalTime.MIN.toNanoOfDay());


这相当于:

LocalDateTime ldtDayStart 
   = localDateTime.with(ChronoField.HOUR_OF_DAY, 0);


一天的结束是非常相似的。只需通过LocalTime.MAX而不是MIN:

// 2019-02-28T23:59:59.999999999
LocalDateTime ldtDayEnd = localDateTime
  .with(ChronoField.NANO_OF_DAY, LocalTime.MAX.toNanoOfDay());


这相当于:

LocalDateTime ldtDayEnd = localDateTime.with(
  ChronoField.NANO_OF_DAY, 86399999999999L);


LocalDate一样,LocalDateTime对象不知道时区。在这种情况下,ZonedDateTime可以帮助:

// 2019-02-28T00:00+08:00[Australia/Perth]
ZonedDateTime ldtDayStartZone = localDateTime
  .with(ChronoField.NANO_OF_DAY, LocalTime.MIN.toNanoOfDay())
  .atZone(ZoneId.of("Australia/Perth"));
// 2019-02-28T23:59:59.999999999+08:00[Australia/Perth]
ZonedDateTime ldtDayEndZone = localDateTime
  .with(ChronoField.NANO_OF_DAY, LocalTime.MAX.toNanoOfDay())
  .atZone(ZoneId.of("Australia/Perth"));

作为奖励,让我们看看 UTC 一天的开始/结束。除了依赖于with()方法的解决方案外,另一个解决方案可以依赖于toLocalDate(),如下所示:

// e.g., 2019-02-28T09:23:10.603572Z
ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
// 2019-02-28T00:00Z
ZonedDateTime dayStartZdt 
  = zdt.toLocalDate().atStartOfDay(zdt.getZone());
// 2019-02-28T23:59:59.999999999Z
ZonedDateTime dayEndZdt = zdt.toLocalDate()
  .atTime(LocalTime.MAX).atZone(zdt.getZone());

由于java.util.DateCalendar存在许多问题,因此建议避免尝试用它们实现此问题的解决方案。



76 两个日期之间的差异

计算两个日期之间的差值是一项非常常见的任务(例如,请参阅“计算年龄”部分)。让我们看看其他方法的集合,这些方法可以用来获得以毫秒、秒、小时等为单位的两个日期之间的差异。



JDK8 之前

建议通过java.util.Date和Calendar类来表示日期时间信息。最容易计算的差异用毫秒表示。绑定到本书的代码包含这样一个解决方案。


从 JDK8 开始

从 JDK8 开始,建议通过Temporal(例如,DateTimeDateLocalTimeZonedDateTime等)来表示日期时间信息。


假设两个LocalDate对象,2018 年 1 月 1 日和 2019 年 3 月 1 日:

LocalDate ld1 = LocalDate.of(2018, 1, 1);
LocalDate ld2 = LocalDate.of(2019, 3, 1);

计算这两个Temporal对象之间差异的最简单方法是通过ChronoUnit类。除了表示一组标准的日期周期单位外,ChronoUnit还提供了几种简便的方法,包括between(Temporal t1Inclusive, Temporal t2Exclusive)。顾名思义,between()方法计算两个Temporal对象之间的时间量。让我们看看计算ld1和ld2之间的差值的工作原理,以天、月和年为单位:


// 424
long betweenInDays = Math.abs(ChronoUnit.DAYS.between(ld1, ld2));
// 14
long betweenInMonths = Math.abs(ChronoUnit.MONTHS.between(ld1, ld2));
// 1
long betweenInYears = Math.abs(ChronoUnit.YEARS.between(ld1, ld2));

或者,每个Temporal公开一个名为until()的方法。实际上,LocalDate有两个,一个返回Period作为两个日期之间的差,另一个返回long作为指定时间单位中两个日期之间的差。使用返回Period的方法如下:

Period period = ld1.until(ld2);
// Difference as Period: 1y2m0d
System.out.println("Difference as Period: " + period.getYears() + "y" 
  + period.getMonths() + "m" + period.getDays() + "d");

使用允许我们指定时间单位的方法如下:

// 424
long untilInDays = Math.abs(ld1.until(ld2, ChronoUnit.DAYS));
// 14
long untilInMonths = Math.abs(ld1.until(ld2, ChronoUnit.MONTHS));
// 1
long untilInYears = Math.abs(ld1.until(ld2, ChronoUnit.YEARS));

ChronoUnit.convert()方法也适用于LocalDateTime的情况。让我们考虑以下两个LocalDateTime对象:2018 年 1 月 1 日 22:15:15 和 2019 年 3 月 1 日 23:15:15:

LocalDateTime ldt1 = LocalDateTime.of(2018, 1, 1, 22, 15, 15);
LocalDateTime ldt2 = LocalDateTime.of(2018, 1, 1, 23, 15, 15);


现在,让我们看看ldt1ldt2之间的区别,用分钟表示:

// 60
long betweenInMinutesWithoutZone 
  = Math.abs(ChronoUnit.MINUTES.between(ldt1, ldt2));


并且,通过LocalDateTime.until()方法以小时表示的差异:

// 1
long untilInMinutesWithoutZone 
  = Math.abs(ldt1.until(ldt2, ChronoUnit.HOURS));


但是,ChronoUnit.between()until()有一个非常棒的地方,那就是它们与ZonedDateTime一起工作。例如,让我们考虑欧洲/布加勒斯特时区和澳大利亚/珀斯时区的ldt1,加上一小时:

ZonedDateTime zdt1 = ldt1.atZone(ZoneId.of("Europe/Bucharest"));
ZonedDateTime zdt2 = zdt1.withZoneSameInstant(
  ZoneId.of("Australia/Perth")).plusHours(1);

现在,我们用ChronoUnit.between()来表示zdt1zdt2之间的差分,用ZonedDateTime.until()来表示zdt1zdt2之间的差分,用小时表示:

// 60
long betweenInMinutesWithZone 
  = Math.abs(ChronoUnit.MINUTES.between(zdt1, zdt2));
// 1
long untilInHoursWithZone 
  = Math.abs(zdt1.until(zdt2, ChronoUnit.HOURS));

最后,让我们重复这个技巧,但是对于两个独立的ZonedDateTime对象:一个为ldt1获得,一个为ldt2获得:

ZonedDateTime zdt1 = ldt1.atZone(ZoneId.of("Europe/Bucharest"));
ZonedDateTime zdt2 = ldt2.atZone(ZoneId.of("Australia/Perth"));
// 300
long betweenInMinutesWithZone 
  = Math.abs(ChronoUnit.MINUTES.between(zdt1, zdt2));
// 5
long untilInHoursWithZone 
  = Math.abs(zdt1.until(zdt2, ChronoUnit.HOURS));



77 实现象棋时钟

从 JDK8 开始,java.time包有一个名为Clock的抽象类。这个类的主要目的是允许我们在需要时插入不同的时钟(例如,出于测试目的)。默认情况下,Java 有四种实现:SystemClock、OffsetClock、TickClock和FixedClock。对于每个实现,Clock类中都有static方法。例如,下面的代码创建了FixedClock(一个总是返回相同Instant的时钟):


Clock fixedClock = Clock.fixed(Instant.now(), ZoneOffset.UTC);


还有一个TickClock,它返回给定时区整秒的当前Instant滴答声:

Clock tickClock = Clock.tickSeconds(ZoneId.of("Europe/Bucharest"));


还有一种方法可以用来在整分钟内打勾tickMinutes(),还有一种通用方法tick(),它允许我们指定Duration

Clock类也可以支持时区和偏移量,但是Clock类最重要的方法是instant()。此方法返回Clock的瞬间:

// 2019-03-01T13:29:34Z
System.out.println(tickClock.instant());



还有一个millis()方法,它以毫秒为单位返回时钟的当前时刻。


假设我们要实现一个时钟,它充当象棋时钟:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QaOdoWVZ-1657077517353)(img/ad6496ac-4425-407c-9a5f-c922283d6bcb.png)]


为了实现一个Clock类,需要遵循以下几个步骤:


   扩展Clock类。

   执行Serializable。

   至少覆盖从Clock继承的抽象方法。


Clock类的框架如下:

public class ChessClock extends Clock implements Serializable {
  @Override
  public ZoneId getZone() {
    ...
  }
  @Override
  public Clock withZone(ZoneId zone) {
    ...
  }
  @Override
  public Instant instant() {
    ...
  }
}


我们的ChessClock将只与 UTC 一起工作;不支持其他时区。这意味着getZone()withZone()方法可以实现如下(当然,将来可以修改):

@Override
public ZoneId getZone() {
  return ZoneOffset.UTC;
}
@Override
public Clock withZone(ZoneId zone) {
  throw new UnsupportedOperationException(
    "The ChessClock works only in UTC time zone");
}


我们实现的高潮是instant()方法。难度在于管理两个Instant,一个是左边的玩家(instantLeft),一个是右边的玩家(instantRight)。我们可以将instant()方法的每一次调用与当前玩家已经执行了一个移动的事实相关联,现在轮到另一个玩家了。所以,基本上,这个逻辑是说同一个玩家不能调用instant()两次。实现这个逻辑,instant()方法如下:


public class ChessClock extends Clock implements Serializable {
  public enum Player {
    LEFT,
    RIGHT
  }
  private static final long serialVersionUID = 1L;
  private Instant instantStart;
  private Instant instantLeft;
  private Instant instantRight;
  private long timeLeft;
  private long timeRight;
  private Player player;
  public ChessClock(Player player) {
    this.player = player;
  }
  public Instant gameStart() {
    if (this.instantStart == null) {
      this.timeLeft = 0;
      this.timeRight = 0;
      this.instantStart = Instant.now();
      this.instantLeft = instantStart;
      this.instantRight = instantStart;
      return instantStart;
    }
    throw new IllegalStateException(
      "Game already started. Stop it and try again.");
  }
  public Instant gameEnd() {
    if (this.instantStart != null) {
      instantStart = null;
      return Instant.now();
    }
    throw new IllegalStateException("Game was not started.");
  }
  @Override
  public ZoneId getZone() {
    return ZoneOffset.UTC;
  }
  @Override
  public Clock withZone(ZoneId zone) {
    throw new UnsupportedOperationException(
      "The ChessClock works only in UTC time zone");
  }
  @Override
  public Instant instant() {
    if (this.instantStart != null) {
      if (player == Player.LEFT) {
        player = Player.RIGHT;
        long secondsLeft = Instant.now().getEpochSecond() 
          - instantRight.getEpochSecond();
        instantLeft = instantLeft.plusSeconds(
          secondsLeft - timeLeft);
        timeLeft = secondsLeft;
        return instantLeft;
      } else {
        player = Player.LEFT;
        long secondsRight = Instant.now().getEpochSecond() 
          - instantLeft.getEpochSecond();
        instantRight = instantRight.plusSeconds(
          secondsRight - timeRight);
        timeRight = secondsRight;
        return instantRight;
      }
    }
    throw new IllegalStateException("Game was not started.");
  }
}


因此,根据哪个玩家调用了instant()方法,代码计算出该玩家在执行移动之前思考所需的秒数。此外,代码会切换播放器,因此下一次调用instant()将处理另一个播放器。

让我们考虑一个从2019-03-01T14:02:46.309459Z开始的国际象棋游戏:

ChessClock chessClock = new ChessClock(Player.LEFT);
// 2019-03-01T14:02:46.309459Z
Instant start = chessClock.gameStart();


此外,玩家执行以下一系列动作,直到右边的玩家赢得游戏:

Left moved first after 2 seconds: 2019-03-01T14:02:48.309459Z
Right moved after 5 seconds: 2019-03-01T14:02:51.309459Z
Left moved after 6 seconds: 2019-03-01T14:02:54.309459Z
Right moved after 1 second: 2019-03-01T14:02:52.309459Z
Left moved after 2 second: 2019-03-01T14:02:56.309459Z
Right moved after 3 seconds: 2019-03-01T14:02:55.309459Z
Left moved after 10 seconds: 2019-03-01T14:03:06.309459Z
Right moved after 11 seconds and win: 2019-03-01T14:03:06.309459Z


看来时钟正确地记录了运动员的动作。

最后,比赛在 40 秒后结束:

Game ended:2019-03-01T14:03:26.350749300Z
Instant end = chessClock.gameEnd();
Game duration: 40 seconds
// Duration.between(start, end).getSeconds();




总结


任务完成了!本章提供了使用日期和时间信息的全面概述。广泛的应用必须处理这类信息。因此,将这些问题的解决方案放在你的工具带下不是可选的。从Date、Calendar到LocalDate、LocalTime、LocalDateTime、ZoneDateTime、OffsetDateTime、OffsetTime、Instant——它们在涉及日期和时间的日常任务中都是非常重要和有用的。



相关文章
|
1月前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
16天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
5天前
|
Java API 调度
Java 日期与时间处理:精准掌控时间流转
Java 8引入了全新的日期和时间API,解决了旧版`java.util.Date`和`Calendar`类设计不佳、操作繁琐的问题。新API包括`LocalDate`、`LocalTime`和`LocalDateTime`类,操作简洁直观,符合日常思维习惯。同时提供了`Period`和`Duration`处理时间间隔,以及`DateTimeFormatter`进行格式化输出。这些改进使开发者能更高效、准确地处理日期和时间,极大提升了开发效率与代码质量。 (239字符)
24 5
|
20天前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
54 12
|
16天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
99 2
|
2月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
1月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
1月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
53 3
|
1月前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
56 4
|
2月前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
72 1
下一篇
开通oss服务