Reputation: 2084
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 ?
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";
}
}
}
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
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
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
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