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



我有下面的bean类

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中存储元素,因为我需要按顺序排序的数据。有没有办法改善这一点。在下面的输出中,最后两个值被重复。

[
{
"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
}
]

br

你工作太辛苦了。

  • 不需要重写任何方法,如equals&hashCode
  • 以后不需要做List。我们可以在某些集合中保持元素有序。
  • 如果指定比较器,则使用有序集自动消除重复项。

关键代码:

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

详细信息为简洁起见,我将使用Java 16+中的记录特性来简要地编写这个类。你也可以使用传统的类。

record ElectricityReading( Instant time , BigDecimal readingKwh ) { }

NavigableSet

如果你想让一个集合的值保持一定的顺序,使用NavigableSet(或SortedSet)。Java提供了一些NavigableSet的实现,其中之一是TreeSet

在构造TreeSet时,传递Comparator,以便可导航集知道您希望如何执行排序。幸运的是,现代Java可以很容易地使用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值的元素,而不考虑BigDecimal值。让我们重复第一个数据元素。我们对readingKwh,-666使用了一个特殊的值,这样我们就可以观察到TreeSet#add的行为。

通过转储到控制台验证。

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

运行时。

读数= [ElectricityReading[time= 20121-10-05t09:00:00 z, readingKwh=1.7], ElectricityReading[time= 20121-10-05t09:00:00 z, readingKwh=2.5574], ElectricityReading[time= 20121-11-05t09:00:00 z, 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 );

有一个名为LinkedHashSet的Java类,它允许您按照条目插入的顺序迭代条目。如果你想让它们以随机顺序插入,然后以有序顺序迭代,那么排序必须发生在某处。在这种情况下,像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;
}

最新更新