比较 Java 中数组列表中的日期后返回最新日期

Return the most recent date after comparing dates from array list in Java

提问人:codrp 提问时间:10/5/2023 最后编辑:Arvind Kumar Avinashcodrp 更新时间:10/11/2023 访问量:152

问:

在比较数组列表中的所有元素后,如何返回最近的日期?预期是返回值,因为它是所有元素中列表中的最新日期。谁能帮忙?2023-01-03T02:00:00.800Z

我使用 JDoodle 创建了一个工作示例

法典

import java.util.ArrayList;

class Car {
  String name;
  String sold;
  Car(String name, String sold) {
    this.name = name;
    this.sold = sold;
  }
}
public class MyClass {
  public static void main(String args[]) {
    ArrayList <Car> carsList = new ArrayList <Car> ();

    carsList.add(new Car("BMW", "2023-01-01T02:00:00.800Z"));
    carsList.add(new Car("Jeep", "2023-01-02T02:00:00.800Z"));
    carsList.add(new Car("Benz", "2023-01-03T02:00:00.800Z"));

    for (int i = 0; i < carsList.size(); i++) {
      for (int j = i + 1; j < carsList.size(); j++) {
        int res = carsList.get(i).sold.compareTo(carsList.get(j).sold);
        System.out.println(res);
      }
    }
  }
}
Java datetime 数组列表比较

评论

0赞 g00se 10/5/2023
我喜欢下面的答案 - 尽可能最新。正如其他人所说,您应该为销售日期使用正确的类型,但由于您使用正确的 ISO 格式,因此您可以按字符串排序SequencedCollectionjava.timeCar latestSold = carsList.stream().max(Comparator.comparing(Car::getSold)).orElseThrow();
0赞 Anonymous 10/5/2023
假设列表中至少有一辆汽车,并且该汽车是 or other 日期时间对象,如几个答案所示:为您提供最近售出的汽车。soldInstantCollcetions.max(carList, Comparator.comparing(Car::getSold))
0赞 g00se 10/5/2023
是的,最好;)

答:

0赞 Aurelien 10/5/2023 #1

您可以通过以下方式替换循环:for

String mostRecentDate = carsList.get(0).sold;

for (int i = 1; i < carsList.size(); i++) {
  String currentDate = carsList.get(i).sold;
  if (currentDate.compareTo(mostRecentDate) > 0) {
    mostRecentDate = currentDate;
  }
}

但老实说,最好的方法是使用以下方法比较日期(因为它是一个日期):soldLocalDateTime

if (currentDate.isAfter(mostRecentDate)) {
    mostRecentDate = currentDate;
}

评论

0赞 codrp 10/5/2023
是的,我们有一个以 UTC 格式提供日期的数据集,因此无法更改它。
1赞 MadProgrammer 10/5/2023
@codrp呢?您可以将 UTC 日期/时间转换为对象,甚至可以将其转换为对象并再次转换回来(到 ),关键是,有(超级有用的)类可以为您完成这项工作 - 就个人而言,我会获取时间戳并将其(至少)转换为 然后将其传递给 - 不应该接受日期值 - 恕我直言,这是一种代码气味 - 仅仅因为你得到了一些东西并不意味着你需要继续使用 - 一旦它超越了数据集,它就是你的了ZonedDateTimeLocalDateTimeZonedDateTimeStringZonedDateTimeCarCarString
0赞 MadProgrammer 10/5/2023
@codrp 我也会谨慎地将 which 与数字进行比较,因为您可能无法获得预期的结果String
1赞 Basil Bourque 10/5/2023
LocalDateTime这里是错误的类型。输入字符串表示从零小时-分钟-秒的 UTC 偏移量中看到的时间日期。A只有这三个部分中的两个:日期和时间,但缺少偏移量(或时区)的概念。 这里其实是合适的类型。LocalDateTimeInstant
1赞 Basil Bourque 10/6/2023
@tquadrat 没有。Any 都可以转换为 ,per toInstant。仅当问题域需要了解该特定时区时才使用。显然,问题域并不关心特定的时区,因为示例数据都是 UTC 格式(注意输入字符串末尾的 )。如果对特定时区不感兴趣,👉🏼则应将某个时刻表示为 。ZonedDateTimeInstantZonedDateTimeZInstant
0赞 Bohemian 10/5/2023 #2

您可以流式传输汽车,提取日期并找到最大日期,如下所示:

String maxDate = carsList.stream()
    .map(car -> car.sold)
    .max(String::compareTo)
    .orElse(null); // return null if no "sold" timestamps found

观看现场演示


您的日期(实际上是时间戳)只能与字符串进行有意义的比较,因为它们是 ISO 格式。使用更合适的类型来存储它们会更好、更清晰,例如 LocalDateTime

评论

2赞 Bohemian 10/5/2023
@user85421当然,但这不是“通用”代码 - 在这种情况下,我们清楚地知道是安全的。get()
1赞 Bohemian 10/5/2023
@OleV.V.更改为 。orElse(null)
1赞 Bohemian 10/5/2023
@user85421更改为 .orElse(null)
3赞 Arvind Kumar Avinash 10/5/2023 #3

我建议您声明为 .但是,如果出于某种原因想将其保留为 a,则可以将其解析为 .请注意,日期时间字符串已采用 ISO 8601 格式;因此,您不需要使用 a 来解析它们。soldInstantStringInstantDateTimeFormatter

carsList.stream()
        .max(Comparator.comparing(car -> Instant.parse(car.sold)))
        .get()
        .sold

在线演示

从 Trail 了解有关新式日期时间 API 的更多信息:日期时间

评论

0赞 tquadrat 10/5/2023
对于实际日期/时间(与时间戳相反),切勿使用 but .对于 UTC,两者可以自由互换,但时间戳永远不会有时区——这对于日期/时间值是强制性的。从技术上讲(就此处的目的而言),它没有太大区别,但从语义上讲,区分时间日期/时间值很重要。InstantZonedDateTime
1赞 Anonymous 10/5/2023
@tquadrat 我同意作出这种区分。我把问题中的字符串看作是即时的,而不是日期和时间,因为它们恰好都在UTC中。该问题没有告知我们任何 tome 区域。OP最清楚,你的评论很有用,谢谢。sold
2赞 Anonymous 10/5/2023
@tquadrat 也许偏离了切线,如果一辆停在芝加哥的汽车通过加利福尼亚的服务器从丹佛的卖家交易到纽约的买家,那么哪个时区是相关的?我的第一个镜头是存储即时,然后根据需要转换为每个时区。
1赞 tquadrat 10/6/2023
@OleV.V.– 如果您使用 ,则使用要使用的时区:它是日期/时间值的一部分。如前所述:区别在于语义上,而不是技术上:a 也可以很容易地移动到任何其他所需的时区,但在不改变数据类型的情况下与 an 不同——因为 an 没有时区(它与 UTC 相同,但没有给它一个时区,尽管打印了一个)。ZonedDateTimeZonedDateTimeInstantInstantInstant::toString
0赞 Basil Bourque 10/6/2023
@tquadrat 你的“时间戳”点是迂腐的。时间戳只能在时区或偏移量的上下文中定义。java.time 中的时间戳概念通过类定义为秒数加上自 1970 年第一时刻以来的纳秒数,如 UTC 偏移量为零。忽略该定义中的“UTC”是不正确的、无用的,而且相当愚蠢。Instant
3赞 Basil Bourque 10/5/2023 #4

其他答案是正确的。但这里有一些替代语法,以及使用正确的日期时间类型。我们看到了 Java 21+ 中的新特性。SequencedCollection

TL的;博士

将类定义为实现 .Comparable

record CarSale( String name , Instant whenSold ) implements Comparable < CarSale >
{
    private static final Comparator < CarSale > COMPARATOR =
            Comparator
                    .comparing ( CarSale :: whenSold )
                    .thenComparing ( CarSale :: name );

    @Override
    public int compareTo ( final CarSale other )
    {
        return CarSale.COMPARATOR.compare ( this , other );
    }
}

将生成的排序集合定义为其实现为 .通过获取该系列的最后一个元素来获得最新的汽车销售。SequencedCollectionTreeSet

CarSale latest =
        new TreeSet <> (
                List
                        .of (
                                new CarSale ( "BMW" , Instant.parse ( "2023-01-01T02:00:00.800Z" ) ) ,
                                new CarSale ( "Benz" , Instant.parse ( "2023-01-03T02:00:00.800Z" ) ) ,
                                new CarSale ( "Jeep" , Instant.parse ( "2023-01-02T02:00:00.800Z" ) )
                        )
        )
                .getLast ( );  // Java 21+. 

CarSale[名称=奔驰,何时售出=2023-01-03T02:00:00.800Z]

命名

你的名字有点含糊不清。可以更清楚,例如.上课实际上是关于汽车何时出售的。所以会更精确。soldwhenSoldCarCarSale

记录

如果类的主要目的是透明地传达浅层不可变数据,请将类定义为记录。编译器隐式创建构造函数 getter 和 。CarequalshashCodetoString

record CarSale ( String name , String whenSold ) {}

java.time.Instant

第二个参数 表示时间轴上的一个时刻和点。Java 包含用于此类日期时间工作的 java.time 类。使用 java.time.Instant 表示时间轴上的一个点,如 UTC 所示(与 UTC 的偏移量为 0 小时-分钟-秒)。whenSold

record CarSale ( String name , Instant whenSold ) {}

输入字符串使用标准 ISO 8601 格式来表示该时刻。默认情况下,java.time 类在解析/生成文本时使用 ISO 8601 格式。因此,我们可以直接解析您的输入。

CarSale car = new CarSale ( "BMW" , Instant.parse( "2023-01-01T02:00:00.800Z" ) ) ;

List.of

您可以使用 List.of 更紧凑地收集实例化对象。这将生成一个不可修改的 List 对象。CarSale

示例数据已按所需的排序顺序排列。因此,我更改了您的原始顺序,以便我们可以验证是否正在进行排序。

List < CarSale > carSales =
        List.of (
                new CarSale ( "BMW" , Instant.parse ( "2023-01-01T02:00:00.800Z" ) ) ,
                new CarSale ( "Benz" , Instant.parse ( "2023-01-03T02:00:00.800Z" ) ),
                new CarSale ( "Jeep" , Instant.parse ( "2023-01-02T02:00:00.800Z" ) ) 
        );

Comparable

其他答案应用比较器。这是一种有效的方法。但是,如果对这些记录进行排序的主要默认方式是 ,则将其作为功能构建到您的类中。若要生成它,请使用其方法实现 Comparable 接口。CarSalewhenSoldcompareTo

为了实现,我们可以定义一个 contained 来做这项工作。使其具有单例常量compareToComparatorstatic

在排序时,当两辆或更多汽车同时售出时,我们可能会打成平局。为了打破这种联系,我们可以看看唯一的其他属性,.whenSoldname

private static final Comparator < CarSale > COMPARATOR =
        Comparator
                .comparing ( CarSale :: whenSold )
                .thenComparing ( CarSale :: name );

完成该方法。compareTo

@Override
public int compareTo ( final CarSale other )
{
    return CarSale.COMPARATOR.compare ( this , other );
}

排序

最后,我们进行排序。

我们有一个不可修改的列表,无法排序。因此,我们需要创建一个可以排序的可修改集合。我们可以通过将不可修改的列表传递给可修改实现的构造函数来做到这一点。List

new ArrayList <> ( carSales )

Stream排序

从那里,我们可以通过创建流进行排序,要求流使用我们对象的“自然顺序”进行自我排序。自然秩序是由它的存在所决定的秩序。我们将生成的排序元素收集到另一个 .CarSaleComparableList

List < CarSale > carSalesSorted = new ArrayList <> ( carSales ).stream ( ).sorted ( ).toList ( );

SequencedCollection

在 Java 21 中,我们可以使用更通用的接口 SequencedCollection 而不是 .请参阅 JEP 431:序列化集合List

SequencedCollection < CarSale > carSalesSorted = new ArrayList <> ( carSales ).stream ( ).sorted ( ).toList ( );

TreeSet代替ArrayList

此时,我们意识到可以使用 SequencedCollection 的不同实现。我们可以使用 TreeSet,它会自动将其元素按排序顺序排列。这只有在我们不关心跟踪重复对象时才有效,因为 Set 实现就像禁止重复一样。我们可以简单地将不可修改项传递给 TreeSet 的构造函数TreeSetList

SequencedCollection < CarSale > carSalesSorted = new TreeSet <> ( carSales );

若要访问元素,请进行迭代。例如,调用 java.lang.Iterable#forEach。这是有效的,因为每个对象也是一个 Iterable 对象。SequencedCollection

carSalesSorted.forEach ( System.out::println );

运行时:

CarSale[name=BMW, whenSold=2023-01-01T02:00:00.800Z]
CarSale[name=Jeep, whenSold=2023-01-02T02:00:00.800Z]
CarSale[name=Benz, whenSold=2023-01-03T02:00:00.800Z]

getLast

通过从我们的 .只需调用 getLastSequencedCollection

CarSale latest = carSalesSorted.getLast ( );

CarSale[名称=奔驰,何时售出=2023-01-03T02:00:00.800Z]


完整的代码示例:

package work.basil.example;

import java.time.Instant;
import java.util.*;

public class CarsLedger
{
    public static void main ( String[] args )
    {
        record CarSale( String name ,
                        Instant whenSold ) implements Comparable < CarSale >
        {
            private static final Comparator < CarSale > COMPARATOR =
                    Comparator
                            .comparing ( CarSale :: whenSold )
                            .thenComparing ( CarSale :: name );

            @Override
            public int compareTo ( final CarSale other )
            {
                return CarSale.COMPARATOR.compare ( this , other );
            }
        }
        List < CarSale > carSales =
                List.of (
                        new CarSale ( "BMW" , Instant.parse ( "2023-01-01T02:00:00.800Z" ) ) ,
                        new CarSale ( "Benz" , Instant.parse ( "2023-01-03T02:00:00.800Z" ) ) ,
                        new CarSale ( "Jeep" , Instant.parse ( "2023-01-02T02:00:00.800Z" ) )
                );

        //SequencedCollection < CarSale > carSalesSorted = new ArrayList <> ( carSales ).stream ( ).sorted ( ).toList ( );

        SequencedCollection < CarSale > carSalesSorted = new TreeSet <> (
                List.of (
                        new CarSale ( "BMW" , Instant.parse ( "2023-01-01T02:00:00.800Z" ) ) ,
                        new CarSale ( "Benz" , Instant.parse ( "2023-01-03T02:00:00.800Z" ) ) ,
                        new CarSale ( "Jeep" , Instant.parse ( "2023-01-02T02:00:00.800Z" ) )
                )
        );

        carSalesSorted.forEach ( System.out :: println );

        CarSale latest = carSalesSorted.getLast ( );
        System.out.println ( "latest = " + latest );
    }
}
0赞 Reilas 10/5/2023 #5

"...在比较数组列表中的所有元素后,如何返回最近的日期?..."

您可以使用 ZonedDateTime#parse 方法。
ba 进行比较,首先按最近日期排序。

import static java.time.ZonedDateTime.parse;
carsList.sort((a, b) -> parse(b.sold).compareTo(parse(a.sold)));

或者,在一行中,有一个

Car c = carsList.stream()
                .sorted((a, b) -> parse(b.sold).compareTo(parse(a.sold)))
                .findFirst()
                .get();

输出

Benz 2023-01-03T02:00:00.800Z

如果不想使用流或闭包,可以使用匿名类

carsList.sort(new Comparator<>() {
    @Override
    public int compare(Car a, Car b) {
        return parse(b.sold).compareTo(parse(a.sold));
    }
});