Java 范围比较的推荐姿势

Posted 明明如月学长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 范围比较的推荐姿势相关的知识,希望对你有一定的参考价值。

一、背景

在平时工作开发过程中,很容易遇到判断某个值是否在某个范围的场景。

如需要校验某个日期是否在某个范围;需要校验某个版本号是否在某个区间;需要校验某个时间点是否在某个时间段内;判断某个人是否属于某个年龄段;判断某个用户的积分是否属于某个等级的区间等。

前一阵子,技术群里有哥们就提了类似的一个问题:

判断当前时间是否在周期的时间段里面有什么好的办法吗 比如 当前时间是2021-10-1 5:00:00 ,设置的时间段为 2021-9-30 1:00:00 -2021-9-30 18:00:00
周期为1天 。 那么每天的5-18点都在周期的时间段里面。 [合十]

有图有真相


群里也有不少同学表达自己的建议

还有

那么,有没有比较优雅的判断方式呢?

二、建议

如果大家花点心思就可以对这些问题进行抽象,即所谓的范围就是数学里面的区间概念,是否在某个范围,即是否在该区间。

因此,我们可以定义一个区间,然后封装一个函数,传入某个值(区间上的某个点),返回是否在这个区间范围。

Guava 中提供了 com.google.common.collect.Range 类,就是为了解决这个问题。

同时还提供了一系列相关类如 RangeSetImmutableRangeSet,可以帮助我们轻松实现区间合并,区间判断是否有重叠,实现区间的不可变特性等,非常强大,超级推荐。

maven 地址
https://mvnrepository.com/artifact/com.google.guava/guava

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>31.0.1-jre</version>
  <!-- or, for android: -->
  <version>31.0.1-android</version>
</dependency>

gradle 依赖

dependencies {
  // Pick one:

  // 1. Use Guava in your implementation only:
  implementation("com.google.guava:guava:31.0.1-jre")

  // 2. Use Guava types in your public API:
  api("com.google.guava:guava:31.0.1-jre")

  // 3. Android - Use Guava in your implementation only:
  implementation("com.google.guava:guava:31.0.1-android")

  // 4. Android - Use Guava types in your public API:
  api("com.google.guava:guava:31.0.1-android")
}

使用非常容易,只要需要比较的类型实现了 Comparable 接口,就直接可构造区间,然后拿值判断是否在这个区间中。

源码地址:
https://github.com/google/guava

主要函数,大家可去源码里自行了解

使用范例:

 public void test_Closed(){
    // 1点20分
    LocalTime start = LocalTime.of(1,20);
    // 8点12分
    LocalTime end = LocalTime.of(8,12);
    // 构造闭区间
    Range<LocalTime> localTimeRange = Range.closed(start,end);

    // 测试
    LocalTime time1 = LocalTime.of(6,4);
    Assert.assertTrue(localTimeRange.contains(time1));

    LocalTime time2 = LocalTime.of(1,20);
    Assert.assertTrue(localTimeRange.contains(time2));

    LocalTime time3 = LocalTime.of(1,19);
    Assert.assertFalse(localTimeRange.contains(time3));

    LocalTime time4 = LocalTime.of(9,19);
    Assert.assertFalse(localTimeRange.contains(time4));
  }

Range 官方的单元测试代码
https://github.com/google/guava/blob/master/guava-tests/test/com/google/common/collect/RangeTest.java

tabnine Range 代码使用范例

https://www.tabnine.com/code/java/classes/com.google.common.collect.Range

Range 还提供了很多方便的函数,还可以对多区间对象进行区交集(com.google.common.collect.Range#isConnected)、并集(com.google.common.collect.Range#intersection)等。

此外对于多段区间操作还提供了 com.google.common.collect.RangeSet 类。通过一个类构造出多个区间,然后传入一个值判断是否命中任意一个区间(com.google.common.collect.RangeSet#contains),是否和另外一个区间有交集(com.google.common.collect.RangeSet#intersects)等,非常方便。

写一个简单的单测:

  public  void testLocalTIme(){
    List<Range<LocalTime>> ranges = new ArrayList<>();
    ranges.add(Range.closed(LocalTime.of(1,5),LocalTime.of(1,15)));
    ranges.add(Range.closed(LocalTime.of(8,25),LocalTime.of(9,15)));
    ranges.add(Range.closed(LocalTime.of(8,55),LocalTime.of(9,35)));

    RangeSet<LocalTime> rangeSet = ImmutableRangeSet.unionOf(ranges);

    Assert.assertTrue(rangeSet.contains(LocalTime.of(1,8)));
    Assert.assertFalse(rangeSet.contains(LocalTime.of(1,3)));
    
    Assert.assertTrue(rangeSet.contains(LocalTime.of(8,58)));
  }

详细使用方式,参考:
https://www.tabnine.com/code/java/classes/com.google.common.collect.RangeSet

还有很多子类型 如 ImmutableRangeSet,不可变的 RangeSet 帮助我们编码。

单测代码
https://github.com/google/guava/blob/master/guava-tests/test/com/google/common/collect/ImmutableRangeSetTest.java

com.google.common.collect.ImmutableRangeSetTest#testSingleBoundedRange

  public void testSingleBoundedRange() {
    ImmutableRangeSet<Integer> rangeSet = ImmutableRangeSet.of(Range.closedOpen(1, 5));

    assertThat(rangeSet.asRanges()).contains(Range.closedOpen(1, 5));

    assertTrue(rangeSet.intersects(Range.closed(3, 4)));
    assertTrue(rangeSet.intersects(Range.closedOpen(0, 2)));
    assertTrue(rangeSet.intersects(Range.closedOpen(3, 7)));
    assertTrue(rangeSet.intersects(Range.greaterThan(2)));
    assertFalse(rangeSet.intersects(Range.greaterThan(7)));

    assertTrue(rangeSet.encloses(Range.closed(3, 4)));
    assertTrue(rangeSet.encloses(Range.closedOpen(1, 4)));
    assertTrue(rangeSet.encloses(Range.closedOpen(1, 5)));
    assertFalse(rangeSet.encloses(Range.greaterThan(2)));

    assertTrue(rangeSet.contains(3));
    assertFalse(rangeSet.contains(5));
    assertFalse(rangeSet.contains(0));

    RangeSet<Integer> expectedComplement = TreeRangeSet.create();
    expectedComplement.add(Range.lessThan(1));
    expectedComplement.add(Range.atLeast(5));

    assertEquals(expectedComplement, rangeSet.complement());
  }

具体大家可以下载源码、查看单元测试案例等,自行学习。

三、总结

建议如果有时间,大家可多看看 Guava 的源码,哪怕只是了解它提供了哪些好用的工具类,也可以帮助我们简化代码,降低出 BUG 的概率。

如果有时间,可以多看看 Guava 中一些核心类的实现原理,并将其思想学习运用到实际的工作开发中。

以上是关于Java 范围比较的推荐姿势的主要内容,如果未能解决你的问题,请参考以下文章

Java 接口的所有子类都需要执行相同处理逻辑的推荐姿势

Java 接口的所有子类都需要执行相同处理逻辑的推荐姿势

Java 单元测试获取目标日志内容进行断言的推荐姿势

开发函数计算的正确姿势——运行 Selenium Java

开发函数计算的正确姿势——运行 Selenium Java

Django 官方推荐的姿势:类视图