Renaud is Not Bill Gates
Renaud is Not Bill Gates

Reputation: 2084

Sort elements in list by enum value or string value

I want to sort an ArrayList in ascending order and in descending order by comparing a String value or an Enum value.

This is the ArrayList I want to sort :

List<Issue> issues;

The list will be sorted depending on two params (field and sort) I give to a function :

private List<Issue> sortList(List<Issue> list, String field, String sort) {
    // My code goes here
}

So let's assume that the field value is title and the sort values is DESC then I want to order all Issue elements in the list by their title filed, this is what I tried :

return list.stream().sorted((i1, i2) -> String.compare(i2.getTitle(), i1.getTitle())).collect(Collectors.toList());

But this generates the following error :

The method compare(String, String) is undefined for the type String

For the enums I couldn't figure out how to compare their values.

How can I solve this ?

Classes definition :

Issue :

public class Issue {

    private IssueElementEnum issueElement;
    private IssueTypeEnum issueType;
    private String title;

    // Getters and setters
}

IssueElementEnum:

public enum IssueElementEnum {
    PROFILE {

        @Override
        public String toString() {
            return "Profil";
        }
    }
    ROLE {

        @Override
        public String toString() {
            return "Rôle";
        }
    },
    User {

        @Override
        public String toString() {
            return "Utilisateur";
        }
    }
}

IssueTypeEnum:

public enum IssueTypeEnum {
    PROFILE {

        @Override
        public String toString() {
            return "Sans Profil";
        }
    },
    ROLE {

        @Override
        public String toString() {
            return "Sans Rôle";
        }
    },
    USER {

        @Override
        public String toString() {
            return "Sans Utilisateur";
        }
    }
}

Edit :

Sometimes I want to sort my list with more than one field, for example (sort the list by title in ascending order and then by issueElement.toSting() in descending order, for that I created the following class :

public class SortDTO implements ISort {

    public static final String ASC = "ASC";
    public static final String DESC = "DESC";

    private String field;

    private String sort;


    public GridSortDTO() {
        this(null, null);
    }

    public GridSortDTO(final String field, final String sort) {
        super();
        this.field = field;
        this.sort = sort;
    }

    @Override
    public String getField() {
        return field;
    }

    @Override
    public void setField(final String field) {
        this.field = field;
    }

    @Override
    public String getSort() {
        return sort;
    }

    @Override
    public void setSort(final String type) {
        this.sort = type;
    }

    @Override
    public String toString() {
        return String.format("Sort[field=%s, sort=%s]", this.field, this.sort);
    }
}

public interface ISort {

    String getField();

    void setField(final String field);

    String getSort();

    void setSort(final String type);
}

Then my sort informations are stored in this array : GridSortDTO[] sorts.

So for example sorts will contain this information :

[{"field":"title","sort":"asc"},{"field":"issueElement","sort":"desc"}]

How can I implement this ?

Upvotes: 0

Views: 9478

Answers (3)

Holger
Holger

Reputation: 298233

It’s not clear which order you want for the enum types, declaration order (PROFILE, ROLE, USER) or lexicographic order of their toString() representation.

In the latter case, you could implement the method as

private List<Issue> sortList(List<Issue> list, String field, String sort) {
    Function<Issue,String> f;
    switch(field) {
        case "Title": f = Issue::getTitle; break;
        case "IssueElement": f = i -> i.getIssueElement().toString(); break;
        case "IssueType": f = i -> i.getIssueType().toString(); break;
        default: throw new IllegalArgumentException("unknown property '"+field+"'");
    }
    Comparator<Issue> cmp = Comparator.comparing(f);
    if("DESC".equalsIgnoreCase(sort)) cmp = cmp.reversed();
    else if(!"ASC".equalsIgnoreCase(sort))
        throw new IllegalArgumentException("invalid sort '"+sort+"'");
    return list.stream().sorted(cmp).collect(Collectors.toList());
}

If you want to use the enum declaration order instead, you have slightly less common code:

private List<Issue> sortList(List<Issue> list, String field, String sort) {
    Comparator<Issue> cmp;
    switch(field) {
        case "Title": cmp = Comparator.comparing(Issue::getTitle); break;
        case "IssueElement": cmp = Comparator.comparing(Issue::getIssueElement); break;
        case "IssueType": cmp = Comparator.comparing(Issue::getIssueType); break;
        default: throw new IllegalArgumentException("unknown property '"+field+"'");
    }
    if("DESC".equalsIgnoreCase(sort)) cmp = cmp.reversed();
    else if(!"ASC".equalsIgnoreCase(sort))
        throw new IllegalArgumentException("invalid sort '"+sort+"'");
    return list.stream().sorted(cmp).collect(Collectors.toList());
}

Instead of the switch statement, you could also maintain a map of existing orders, which offers more flexibility:

// in Java 9, you should replace Arrays.asList(...) with List.of(...)
static final Map<List<String>,Comparator<Issue>> ORDER;
static {
    Map<List<String>,Comparator<Issue>> m = new HashMap<>();
    Comparator<Issue> c = Comparator.comparing(Issue::getTitle);
    m.put(Arrays.asList("Title", "asc"), c);
    m.put(Arrays.asList("Title", "desc"), c.reversed());
    c = Comparator.comparing(Issue::getIssueElement);
    m.put(Arrays.asList("IssueElement", "asc"), c);
    m.put(Arrays.asList("IssueElement", "desc"), c.reversed());
    c = Comparator.comparing(Issue::getIssueType);
    m.put(Arrays.asList("IssueType", "asc"), c);
    m.put(Arrays.asList("IssueType", "desc"), c.reversed());
    ORDER = Collections.unmodifiableMap(m);
}
private List<Issue> sortList(List<Issue> list, String field, String sort) {
    Comparator<Issue> cmp = ORDER.get(Arrays.asList(field, sort.toLowerCase(Locale.ROOT)));
    if(cmp == null)
        throw new IllegalArgumentException("property '"+field+"', sort '"+sort+"'");
    return list.stream().sorted(cmp).collect(Collectors.toList());
}

This approach can be adapted to your new requirement, though, I strongly suggest a slight redesign:

enum Direction { ASCENDING, DESCENDING }
public interface ISort {
    String getField();
    void setField(final String field);
    Direction getSort();
    void setSort(final Direction type);
}

Adapting the implementation is straight-forward, but you should avoid allowing null for the sorting direction, as then, it’s intrisically only either of the two legal values:

public class SortDTO implements ISort {
    private String field;
    private Direction sort;
    public SortDTO() { this(null, Direction.ASCENDING); }
    public SortDTO(String field, Direction sort) {
        this.field = field;
        this.sort = sort;
    }
    public String getField() { return field; }
    public void setField(String field) { this.field = field; }
    public Direction getSort() { return sort; }
    public void setSort(Direction sort) { this.sort = Objects.requireNonNull(sort); }
    @Override
    public String toString() {
        return String.format("Sort[field=%s, sort=%s]", this.field, this.sort);
    }
}

We augment these types with an immutable key type capable of capturing the current state of an ISort implementation and having proper equals and hashCode implementations:

final class SortKey {
    final String field;
    final Direction direction;
    private SortKey(String f, Direction d) { field=f; direction=d; }
    @Override
    public int hashCode() {
        return field.hashCode()*2+direction.ordinal();
    }
    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(!(obj instanceof SortKey)) return false;
        SortKey that = (SortKey)obj;
        return this.direction == that.direction && this.field.equals(that.field);
    }
    static SortKey of(String field, Direction dir) {
        return new SortKey(Objects.requireNonNull(field), Objects.requireNonNull(dir));
    }
    static SortKey of(ISort s) {
        return of(s.getField(), s.getSort());
    }
}

Then, the adapted solution may look like

static final Map<SortKey,Comparator<Issue>> ORDER;
static {
    Map<SortKey,Comparator<Issue>> m = new HashMap<>();
    Comparator<Issue> c = Comparator.comparing(Issue::getTitle);
    m.put(SortKey.of("Title", Direction.ASCENDING), c);
    m.put(SortKey.of("Title", Direction.DESCENDING), c.reversed());
    c = Comparator.comparing(Issue::getIssueElement);
    m.put(SortKey.of("IssueElement", Direction.ASCENDING), c);
    m.put(SortKey.of("IssueElement", Direction.DESCENDING), c.reversed());
    c = Comparator.comparing(Issue::getIssueType);
    m.put(SortKey.of("IssueType", Direction.ASCENDING), c);
    m.put(SortKey.of("IssueElement", Direction.DESCENDING), c.reversed());
    ORDER = Collections.unmodifiableMap(m);
}
private List<Issue> sortList(List<Issue> list, ISort... order) {
    if(order.length == 0) return new ArrayList<>(list);
    Comparator<Issue> cmp = ORDER.get(SortKey.of(order[0]));
    if(cmp == null) throw new IllegalArgumentException(order[0].toString());
    for(int ix = 1; ix < order.length; ix++) {
        Comparator<Issue> next = ORDER.get(SortKey.of(order[ix]));
        if(next == null) throw new IllegalArgumentException(order[ix].toString());
        cmp = cmp.thenComparing(next);
    }
    return list.stream().sorted(cmp).collect(Collectors.toList());
}

This allows an arbitrary number of sort criteria, the first being the primary order, the second being the secondary order and so on.

Upvotes: 4

Anton Balaniuc
Anton Balaniuc

Reputation: 11739

If you want to sort your list by the title just use the following:

stream().sorted(Comparator.comparing(Issue::getTitle)).collect(Collectors.toList())

Please have a look at java-8 Comparator

Upvotes: 2

viniciusjssouza
viniciusjssouza

Reputation: 1275

Actually, the method to compare two strings that you are trying to use is compareTo and not compare, which is defined in the Comparable interface. Take a look in the javadoc: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html

Upvotes: 2

Related Questions