简体   繁体   中英

How to sort by two fields one of which is enum?

I need to sort a List in alphabetical order by name and then to sort it one more time by type and put on the top of the list elements with specific type. This is what I have done so far, but it didn't work as expected and it returns the list sorted only by name.

  public List<PRDto> present(
      List<ParticipantReference> participants) {
    return participants.stream()
        .map(MAPPER::toDto)
        .sorted(Comparator.comparing(PRDto::getName)
            .thenComparing(PRDto::getParticipantType, (type1, type2) -> {
              if (type1.equals(type2)) {
                return 0;
              }
              if (type2.equals(ParticipantType.S.getDescription())) {
                return 1;
              }
              if (type1.equals(ParticipantType.S.getDescription())) {
                return -1;
              }
              return type1.compareTo(type2);
            }))
        .collect(toList());
    }
    

This is my enum:

@Getter
public enum ParticipantType {
  F("F"),
  D_F("D+F"),
  S("S");

  private final String description;

  ParticipantType(String description) {
    this.description = description;
  }
}

Note that if you want to sort by type first and then by name you need to do so. Doing it the other way round would require the sort algorithm to keep the existing order for equal elements which might not always be guaranteed.

It would also be easier to map the enum to an integer representing the order and let Comparator do the work.

So your code could look like this:

...
.sorted(Comparator.comparingInt(p -> { //first compare by mapped type
     switch( p.getParticipantType() ) {
       case S: return 0;
       default: return Integer.MAX_VALUE; //so you could insert other enum values if needed
     }
   }).thenComparing(PRDto::getName) //then compare by name
)
... 

If you use a composite comparator like this it will put elements with type S at the top. If you'd write that comparator the "old" way it could look like this:

compare(PRDto left, PRDto right) {
  int typeOrderLeft = mapToInt(left.getParticipantType()); //mapToInt contains the mapping expression, i.e the switch in the example above
  int typeOrderRight = mapToInt(right.getParticipantType());
  
  //first order by type
  int result = Integer.compare(typeOrderLeft, typeOrderRight);

  //if the type order is the same, i.e. both have S or non-S, sort by name
  if( result == 0 ) { 
    result = String.compare(left.getName(), right.getName());
  }

  return result; 
}

Example (already ordered):

type  name
---------------
S     zulu      //S < F and S < D_F due to the mapping, name is irrelevant here
D_F   alpha     //F and D_F are considered equal, so names are compared
F     bravo

To preserve readability I would not leave the comparison in the stream pipeline but extract it in a comparator

 public List<PRDto> present(List<ParticipantReference> participants) {

    Comparator<PRDto> byType = Comparator.comparing(o -> !o.getType().equals(ParticipantType.S));

    Comparator<PRDto> byName = Comparator.comparing(PRDto::getName);

    return participants.stream().sorted(byType.thenComparing(byName)).collect(toList());
}

My answer had unnecessary redundant logic, which @Holger thankfully pointed out to me. This could also be inlined as Holger mentions it in the comments to:

return participants.stream().sorted(
                   Comparator.comparing((PRDto o) -> !o.getType().equals(ParticipantType.S))
                             .thenComparing(PRDto::getName))
            .collect(toList());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM