Geoffrey De Smet
Geoffrey De Smet

Reputation: 27312

Comparator.comparing(...) of a nested field

Suppose I have a domain model like this:

class Lecture {
     Course course;
     ... // getters
}

class Course {
     Teacher teacher;
     int studentSize;
     ... // getters
}

class Teacher {
     int age;
     ... // getters
}

Now I can create a Teacher Comparator like this:

    return Comparator
            .comparing(Teacher::getAge);

But how do I compare Lecture's on nested fields, like this?

    return Comparator
            .comparing(Lecture::getCourse::getTeacher:getAge) 
            .thenComparing(Lecture::getCourse::getStudentSize);

I can't add a method Lecture.getTeacherAge() on the model.

Upvotes: 49

Views: 44680

Answers (4)

MauroB
MauroB

Reputation: 580

I created this method which sorts using a field and a field of a nested object. This solution is actually compliant with Sonar too.

public static List<Parcel> getSortedParcelsListBySaleOrderNumberAndShipperParcelTracking(final List<Parcel> parcelList) {
    Comparator<Parcel> comparator = Comparator
            .<Parcel, String>comparing(p -> p.getPickOrderCustomer().getSaleOrderNumber())
            .thenComparing(Parcel::getShipperParcelTracking);
    return parcelList.stream().sorted(comparator).toList();
}

Upvotes: 3

manish garg
manish garg

Reputation: 11

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;

  class Person {
      String name ;
      PersonalDetail pDetail;
    public Person(String name, PersonalDetail pDetail) {
        super();
        this.name = name;
        this.pDetail = pDetail;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public PersonalDetail getpDetail() {
        return pDetail;
    }
    public void setpDetail(PersonalDetail pDetail) {
        this.pDetail = pDetail;
    }




  }

  class PersonalDetail{
      BirthDate birthDate;

    public BirthDate getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(BirthDate birthDate) {
        this.birthDate = birthDate;
    }

    public PersonalDetail(BirthDate birthDate) {
        super();
        this.birthDate = birthDate;
    }


  }

    class BirthDate {
        public String getBirthdate() {
            return birthdate;
        }

        public void setBirthdate(String birthdate) {
            this.birthdate = birthdate;
        }

    String birthdate;

        public BirthDate(String birthdate) {
            super();

            this.birthdate = birthdate;
        }  
  }

  public class Test1 {
       public static void main(String[] args) {
           BirthDate b1 = new BirthDate("2019-08-08");
           BirthDate b2 = new BirthDate("2025-09-09");
           BirthDate b3 = new BirthDate("2025-09-08");
           BirthDate b4 = new BirthDate("2024-09-08");

           PersonalDetail pd1  = new PersonalDetail(b1);
           PersonalDetail pd2  = new PersonalDetail(b2);
           PersonalDetail pd3  = new PersonalDetail(b3);
           PersonalDetail pd4  = new PersonalDetail(b4);

           Person p1  = new Person("P1",pd1);
           Person p2  = new Person("P2",pd2);
           Person p3  = new Person("P3",pd3);
           Person p4  = new Person("P4",pd4);

           List<Person> persons = new ArrayList();
           persons.add(p1);
           persons.add(p2);
           persons.add(p3);
           persons.add(p4);

           Function<Person, PersonalDetail> getCourse = Person::getpDetail;  

           Person minByAge = persons.stream()
                      .max(Comparator.comparing(getCourse.andThen(PersonalDetail::getBirthDate).andThen(BirthDate::getBirthdate))).get();


          System.out.println(maxByAge.getName());

       }

    }

Upvotes: 0

Eran
Eran

Reputation: 393846

You can't nest method references. You can use lambda expressions instead:

return Comparator
        .comparing(l->l.getCourse().getTeacher().getAge(), Comparator.reverseOrder()) 
        .thenComparing(l->l.getCourse().getStudentSize());

Without the need for reverse order it's even less verbose:

return Comparator
        .comparing(l->l.getCourse().getTeacher().getAge()) 
        .thenComparing(l->l.getCourse().getStudentSize());

Note: in some cases you need to explicitly state the generic types. For example, the code below won't work without the <FlightAssignment, LocalDateTime> before comparing(...) in Java 8.

flightAssignmentList.sort(Comparator
        .<FlightAssignment, LocalDateTime>comparing(a -> a.getFlight().getDepartureUTCDateTime())
        .thenComparing(a -> a.getFlight().getArrivalUTCDateTime())
        .thenComparing(FlightAssignment::getId));

Newer java version have better auto type detection and might not require that.

Upvotes: 73

Nazarii Bardiuk
Nazarii Bardiuk

Reputation: 4342

Unfortunately there is no nice syntax in java for that.

If you want to reuse parts of comparator I can see 2 ways:

  • by composing comparators

    return comparing(Lecture::getCourse, comparing(Course::getTeacher, comparing(Teacher::getAge)))
           .thenComparing(Lecture::getCourse, comparing(Course::getStudentSize));
    
    // or with separate comparators
    Comparator<Teacher> byAge = comparing(Teacher::getAge);
    Comparator<Course> byTeacherAge = comparing(Course::getTeacher, byAge);
    Comparator<Course> byStudentsSize = comparing(Course::getStudentSize);
    return comparing(Lecture::getCourse, byTeacherAge).thenComparing(Lecture::getCourse, byStudentsSize);
    
  • by composing getter functions

    Function<Lecture, Course> getCourse = Lecture::getCourse;            
    return comparing(getCourse.andThen(Course::getTeacher).andThen(Teacher::getAge))
           .thenComparing(getCourse.andThen(Course::getStudentSize));
    
    // or with separate getters
    Function<Lecture, Course> getCourse = Lecture::getCourse;
    Function<Lecture, Integer> teacherAge = getCourse.andThen(Course::getTeacher).andThen(Teacher::getAge);
    Function<Lecture, Integer> studentSize = getCourse.andThen(Course::getStudentSize);
    return comparing(teacherAge).thenComparing(studentSize);
    

Upvotes: 20

Related Questions