按排序顺序将自定义 class 的数据存储在 HashSet 中

Storing Data of custom class in HashSet in sorted order

我有下面的豆子class

public class ElectricityReading {

    private Instant time;
    private BigDecimal reading; // kW

    public ElectricityReading() { }

    public ElectricityReading(Instant time, BigDecimal reading) {
        this.time = time;
        this.reading = reading;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ElectricityReading that = (ElectricityReading) o;
        return Objects.equals(time, that.time);
    }

    @Override
    public int hashCode() {
        return Objects.hash(time);
    }

    public BigDecimal getReading() {
        return reading;
    }

    public Instant getTime() {
        return time;
    }

    public void setReading(BigDecimal reading){
        this.reading=reading;
    }
}

我需要将这些结果存储在 HashSet 中。实际上我不想存储 time 两次。如果它重复我需要忽略或覆盖(两者都可以工作,它的 POC)。这就是我花时间研究 equalshashCode 方法的原因。 我正在编写如下 HashSet 代码。

public List<ElectricityReading> generate(int number) {
        Set<ElectricityReading>reading= new HashSet<>();

        Instant now = Instant.now();

        Random readingRandomiser = new Random();
        for (int i = 0; i < number; i++) {
            double positiveRandomValue = Math.abs(readingRandomiser.nextGaussian());
            BigDecimal randomReading = BigDecimal.valueOf(positiveRandomValue).setScale(4, RoundingMode.CEILING);
            ElectricityReading electricityReading = new ElectricityReading(now.minusSeconds(i * 10), randomReading);
            if(!reading.contains(electricityReading.getTime())){
                reading.add(electricityReading);
            }else {
                electricityReading.setReading(electricityReading.getReading());
            }
        }
        List<ElectricityReading> readings = new ArrayList<>(reading);
        readings.sort(Comparator.comparing(ElectricityReading::getTime));
        return readings;
    }

我将元素存储在 List 中,因为我需要按排序顺序排列数据。无论如何要改善这一点。 在下面的输出中,最后 2 个值正在重复。

[
    {
        "time": "2021-09-15T20:13:51.268560800Z",
        "reading": 2.5574
    },

    {
        "time": "2020-11-29T08:00:00Z",
        "reading": 1.7
    },
    {
        "time": "2020-11-29T08:00:00Z",
        "reading": 1.7
    }
]

有一个名为 LinkedHashSet 的 Java class 可以让您按照插入的顺序迭代项目。如果您希望它们以随机顺序插入,然后按排序顺序迭代,排序将不得不在某处 发生。在这种情况下,像 TreeSet 这样的有序集合可能是更好的选择?

您实际上不需要集合,因为您有一个局部变量 now 并且您正在从中减去 i * 10 秒,您将不会有重复的时间条目。在实际实现中不做太多更改,只需执行以下操作:

public List<ElectricityReading> generate(int number) {
    List<ElectricityReading> readings = new ArrayList<>();
    Instant now = Instant.now();
    Random readingRandomiser = new Random();

    for (int i = 0; i < number; i++) {
        double positiveRandomValue = Math.abs(readingRandomiser.nextGaussian());
        BigDecimal randomReading = BigDecimal.valueOf(positiveRandomValue).setScale(4, RoundingMode.CEILING);
        ElectricityReading electricityReading = new ElectricityReading(now.minusSeconds(i * 10), randomReading);
        readings.add(electricityReading);
    }
    readings.sort(Comparator.comparing(ElectricityReading::getTime));
    return readings;
}

tl;博士

你太辛苦了

  • 无需覆盖任何方法,例如 equals & hashCode
  • 以后不用做List了。我们可以让元素按特定类型的集合排序。
  • 如果我们指定比较器,使用有序集会自动消除重复项。

关键代码:

NavigableSet < ElectricityReading > readings = 
    new TreeSet <>( 
        Comparator.comparing( ElectricityReading :: time ) 
    )
;

详情

为简洁起见,我将使用Java 16+中的records特性来简单写下class。您也可以使用传统的 class.

record ElectricityReading( Instant time , BigDecimal readingKwh ) { }

NavigableSet

如果您希望集合按特定顺序保存其值,请使用 NavigableSet (or SortedSet). Java comes with a few implementations of NavigableSet, one of which is TreeSet

构造TreeSet时,传递采用方法引用的Comparator so the navigable set knows how you want the sorting performed. Fortunately, modern Java makes creating a comparator quite easy with the Comparator.comparing方法。我们使用我们的 getter 方法来访问我们的 Instant 字段,ElectricityReading#time(由编译器在记录中隐式创建),作为我们比较的方法参考。

NavigableSet < ElectricityReading > readings = new TreeSet <>( Comparator.comparing( ElectricityReading :: time ) );

让我们用一些示例数据来尝试一下。我们故意将数据设置为无序,以验证我们的集合是否正确排序。请注意 Instant 值和 readingKwh 值都在并行增加。

为了便于阅读,我修改了您的输入数据。

readings.add( new ElectricityReading( Instant.parse( "2021-09-05T08:00:00Z" ) , new BigDecimal( "1.7" ) ) );
readings.add( new ElectricityReading( Instant.parse( "2021-09-05T08:00:00Z" ) , new BigDecimal( "-666" ) ) );    // Repeated `Instant` value.
readings.add( new ElectricityReading( Instant.parse( "2021-11-05T09:00:00Z" ) , new BigDecimal( "3.1" ) ) );     // Out-of-order.
readings.add( new ElectricityReading( Instant.parse( "2021-10-05T09:00:00Z" ) , new BigDecimal( "2.5574" ) ) );

您还要求阻止集合中具有相同 Instant value, without regard to the BigDecimal value. So let's repeat that first data element. We use a special value for readingKwh, -666, so we can observe the behavior of TreeSet#add.

的元素

通过转储到控制台进行验证。

System.out.println( "readings = " + readings );

当运行.

readings = [ElectricityReading[time=2021-09-05T08:00:00Z, readingKwh=1.7], ElectricityReading[time=2021-10-05T09:00:00Z, readingKwh=2.5574], ElectricityReading[time=2021-11-05T09:00:00Z, readingKwh=3.1]]

我们看到两种效果,都是需要的:

  • 集合中的元素按 Instant 字段的时间顺序迭代。
  • 重复的对象被忽略了,因为我们看到集合保持 1.7 读数。我们的 TreeSet 集合拒绝了我们使用 -666 添加对象的尝试,因为它的 Instant 值已经在 NavigableSet.
  • 的现有元素中找到

为了方便复制粘贴,将所有代码放在一起。

record ElectricityReading( Instant time , BigDecimal readingKwh ) { }
NavigableSet < ElectricityReading > readings = new TreeSet <>( Comparator.comparing( ElectricityReading :: time ) );

readings.add( new ElectricityReading( Instant.parse( "2021-09-05T08:00:00Z" ) , new BigDecimal( "1.7" ) ) );
readings.add( new ElectricityReading( Instant.parse( "2021-09-05T08:00:00Z" ) , new BigDecimal( "-666" ) ) );    // Repeated `Instant` value.
readings.add( new ElectricityReading( Instant.parse( "2021-11-05T09:00:00Z" ) , new BigDecimal( "3.1" ) ) );     // Out-of-order.
readings.add( new ElectricityReading( Instant.parse( "2021-10-05T09:00:00Z" ) , new BigDecimal( "2.5574" ) ) );

System.out.println( "readings = " + readings );