1
class Person {
    String name;
    String id;
    String dateOfBirth;
    int salary;
 }

 List <Person> listOfPersons; // Consider that list is filled with some data

e.g. dateOfBirth = "2018-12-10T13:49:51.141Z";

Date of birth is in ISO8601 and can be converted to java.util.Date with Date.from(Instant.parse(dateOfBirth)); if that can help to sort the list.

So is there any way of using streams or nice solution in JAVA 8 to get the youngest Person from list of persons?

Additional comment: Person class can not be changed.

Lii
  • 11,553
  • 8
  • 64
  • 88
  • Make `Person` implement `Comparable`, compare the String to a `LocalDateTime` (avoid the old `java.util.Date`) and delegate the comparison so the `LocalDateTime` instances. – f1sh Jun 22 '22 at 13:43
  • 5
    A birthdate really shouldnt be a `Date` object - this class is legacy, dont use it. It should be a `LocalDate` object instead. Or, if you actually want to capture the moment in time it happened, use an `Instant`. Tldr, learn your date/time-types. – Zabuzard Jun 22 '22 at 13:47
  • Use a `LocalDate` here, it's the most suitable class because it only holds *date* values: day of month, month of year and year. Btw, your `dateOfBirth` even has a time zone / offset, which might have to be handled when parsing it to, let's say, a `ZonedDateTime`. – deHaar Jun 22 '22 at 13:48
  • I can not change Person class. Thing is that I have Date of birth in ISO8601, and I can not change that also. I put person class so you all understand easy what is bordering me. So my problem is I need to get salary from the youngest Person. – Vanja Radovanovic Jun 22 '22 at 13:53
  • 1
    [Sort ArrayList of custom Objects by property](https://stackoverflow.com/questions/2784514/sort-arraylist-of-custom-objects-by-property) – OH GOD SPIDERS Jun 22 '22 at 13:58
  • 3
    @VanjaRadovanovic None of what you just said changes anything here. What we are saying is that you should not parse your `String dateOfBirth` as `Date` object. But as `LocalDate` or `Instant` instead. All of them can easily parse ISO-8601 compliant strings. And then you can sort on those. If the string truly looks like what you showed, i.e. with full time and zone info in it, and all you want is to sort it chronologically, I would go for `Instant.parse(dateOfBirth)`. For example `persons.stream().min(Comparator.comparing(person -> Instant.parse(dateOfBirth))).map(person -> person.salary);`. – Zabuzard Jun 22 '22 at 13:58
  • 5
    Why should he even parse the date at all? The good thing about ISO-8601 dates is that their alphabetic order is the same as their natural order. He can just sort on the String without doing any parsing. – OH GOD SPIDERS Jun 22 '22 at 14:03
  • @OHGODSPIDERS That is an interesting property, I was not aware either. Cool. – Zabuzard Jun 22 '22 at 14:07

4 Answers4

5

You can do like this

Person youngestPerson = listOfPersons.stream()
.reduce((p1, p2) -> LocalDateTime.parse(p1.getStringDateofBirth()).isAfter(LocalDateTime.parse(p2.getStringDateofBirth()))?p1:p2)
.orElse(null)
AGH
  • 89
  • 5
  • plus 1 and btw you can also use `stream().max()` rather than `reduce()` (and `Comparator` has some handy utility functions too for building them up). – Jason C Jun 22 '22 at 15:01
  • To properly parse the date you should change `LocalDateTime` to either `Instant` ,`ZonedDateTime`, or `OffsetDateTime`. – WJS Jun 22 '22 at 20:59
5

UTC ISO-8601 date strings can be compared lexicographically. The most elegant Java 8 solution in my opinion would therefore be:

Person youngest = listOfPersons.stream()
    .max(Comparator.comparing(p -> p.dateOfBirth)).get();

If you don't trust the string comparison, or your timezones are all over the place, you can parse the date if you wish:

Person youngest = listOfPersons.stream()
    .max(Comparator.comparing(p -> LocalDateTime.parse(p.dateOfBirth, DateTimeFormatter.ISO_DATE_TIME)))
    .get();

Or even:

Person youngest = listOfPersons.stream()
    .max(Comparator.comparing(p -> Instant.parse(p.dateOfBirth))).get();

Note that you should probably use LocalDateTime (or Instant) rather than Date, as Java 8 has moved away from the old date/time APIs (a good rundown here). You'll need to use the ISO_DATE_TIME formatter to handle the Z zone at the end if you're using LocalDateTime.

Runnable example is here.


API of interest:

  • Stream is Java 8's sort of ... iterable transformation interface supporting lots of handy methods and method chaining. All Collection objects have a stream() member for obtaining one.
  • Stream.max() returns the maximum value in a stream according to some Comparator that you specify (which could be a lambda function, or, as in the above case, just a Comparator object).
    • The return value is actually an Optional<Person> in this case (e.g. if the list was empty there might be no value), so get() gets the value, or you could just leave it as an Optional if you want, or you could use e.g. orElse(null) instead of get() if you'd rather it be null for an empty list.
  • Comparator.comparing() is a handy little utility that constructs a Comparator<T> given a function that maps a T to a comparable value.

So in the above code:

  • p -> p.dateOfBirth (or p -> LocalDateTime.parse(p.dateOfBirth), your call) is a lambda function which maps a person to their birthdate.
  • Comparator.comparing(...), when given said key mapping function, returns a Comparator that compares the birth date of two persons, performing the appropriate comparison depending on whether you used the String or the LocalDateTime version.
  • max() returns the object with the maximum date, i.e. the youngest person.
  • get() just gets the Person out of the Optional<Person>.

Full example (linked to ideone above):

import java.util.*;
import java.lang.*;
import java.io.*;
import java.time.*;
import java.time.format.*;

class Ideone {

    public static void main (String[] args) throws java.lang.Exception {
        
        Person youngest;
        
        // you could do string comparison if you know the timezones are consistent:
        
        youngest = listOfPersons.stream()
            .max(Comparator.comparing(p -> p.dateOfBirth)).get();
        
        System.out.println("youngest is: " + youngest.name + " " + youngest.dateOfBirth);

        // or you could just parse the date, a safer option:
        
        youngest = listOfPersons.stream()
            .max(Comparator.comparing(p -> LocalDateTime.parse(p.dateOfBirth, DateTimeFormatter.ISO_DATE_TIME))).get();
        
        System.out.println("youngest is: " + youngest.name + " " + youngest.dateOfBirth);
        
    }

    
    //------------------------------------
    // types from OP:
    
    static class Person{
        String name;
        String id;
        String dateOfBirth;
        int salary;
    }

    static List <Person> listOfPersons;

    
    //------------------------------------
    // boring example data initialization:
    
    // make an iso birthdate string given an age (for initializing data)
    static String fromAge (int years) {
        //return LocalDateTime.now().minusYears(years).toString();
        // add zone to emulate OP's input:
        return LocalDateTime.now().minusYears(years).toString() + 'Z';
    }
    
    // make up some data
    static {
        listOfPersons = List.of(
            // fine for example, but i wouldn't init like this in production
            new Person() {{ name="helga"; dateOfBirth=fromAge(22); }},
            new Person() {{ name="mortimer"; dateOfBirth=fromAge(48); }},
            new Person() {{ name="gertrude"; dateOfBirth=fromAge(6); }},
            new Person() {{ name="jebediah"; dateOfBirth=fromAge(39); }}
        );
        listOfPersons.forEach(p -> System.out.println(p.name + ' ' + p.dateOfBirth));
    }

    
} 

Outputs:

helga 2000-06-23T03:07:26.084850Z
mortimer 1974-06-23T03:07:26.126628Z
gertrude 2016-06-23T03:07:26.126894Z
jebediah 1983-06-23T03:07:26.127948Z
youngest is: gertrude 2016-06-23T03:07:26.126894Z
youngest is: gertrude 2016-06-23T03:07:26.126894Z
Jason C
  • 38,729
  • 14
  • 126
  • 182
3

As suggested by others, use LocalDateTime. LocalDateTime has very conveniant functions, that fit exactly your problem. You can directly parse your String into it, just remove the "Z" from your ISO string and you're good:

LocalDateTime someBirthday = LocalDateTime.parse("2018-12-10T13:49:51.141");

Next step, write a comparator:

public class BirthdayComparator implements Comparator<LocalDateTime> {

    @Override
    public int compare(LocalDateTime date1, LocalDateTime date2) {
        if(date1.isAfter(date2)) {
            return 1;
        } else if (date1.equals(date2)) {
            return 0;
        } else {
            return -1;
        }
    }
}

Thats it. You can now sort for birth dates.

Update

One last word of caution: This code ignores time zones. To be precise, it implicitly assumes, all given birthdays are in the same time zone. This can be problematic, if you have to deal with persons all over the globe...

SimpleJack
  • 167
  • 1
  • 8
  • 5
    `LocalDateTime` is already `Comparable`. No need to write a Comparator for it :) – Zabuzard Jun 22 '22 at 14:08
  • Ah OK, didn't know that. Well..., then just parse string. – SimpleJack Jun 22 '22 at 14:12
  • You don't need to do a full sort; you can use `Stream.max()`. That way you won't modify the original list and also, performance wise, it's just a linear search for a maximum rather than a complete sort. – Jason C Jun 22 '22 at 14:59
2

Here is one way.

record Person(String name, String id, String dateOfBirth,
                int salary) {}
        
List<Person> list = new ArrayList<>(List.of(
        new Person("Jim", "10", "2018-12-10T13:49:51.141Z",
                20),
        new Person("Mary", "11", "2018-12-15T13:49:51.141Z",
                20),
        new Person("Bob", "12", "2018-12-12T13:49:51.141Z",
                20)));
  • use maxBy since the youngest person would be born futher away from the epoch.
  • parse with an Instant of time. You cannot parse your given format example using LocalDateTime
Person youngest = list.stream().collect(Collectors.maxBy(Comparator
        .comparing(p -> Instant.parse(p.dateOfBirth)))).get();

System.out.println(youngest);

prints

Person[name=Mary, id=11, dateOfBirth=2018-12-15T13:49:51.141Z, salary=20]

Note that maxBy returns an Optional, therefore the get() call at the end. You may want to return the Optional<Person> and then extract the date based on success of the lookup.

WJS
  • 36,363
  • 4
  • 24
  • 39