Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting a collection in a generic method in Java 8

The following method performs ordering.

public List<Comparator<Entity>> sort(Map<String, String> map) {
    List<Comparator<Entity>> list = new ArrayList<Comparator<Entity>>();

    for (Map.Entry<String, String> entry : map.entrySet()) {
        boolean sortOrder = entry.getValue().equalsIgnoreCase("asc");

        switch (entry.getKey()) {
            case "id":
                list.add(sortOrder ? Comparator.comparing(Entity::getId) : Comparator.comparing(Entity::getId, Comparator.reverseOrder()));
                break;
            case "size":
                list.add(sortOrder ? Comparator.comparing(Entity::getSize) : Comparator.comparing(Entity::getSize, Comparator.reverseOrder()));
                //break;
        }
    }

    return list;
}

The list being returned by the above method is used in the following way.

// map is initialized somewhere based on client's interactions with sorting.
// Based on client's interactions, map may be empty or it may contain one or more ordering fields.

if (MapUtils.isNotEmpty(map)) {  // map = new LinkedHashMap<String, String>();

    List<Comparator<Entity>> comparators = sort(map);
    Comparator<Entity> comparator = comparators.remove(0);

    for (Comparator<Entity> c : comparators) {
        comparator = comparator.thenComparing(c);
    }

    list = list.stream().sorted(comparator).collect(Collectors.toList());
} else {
    // This is the default ordering.
    list = list.stream().sorted(Comparator.comparing(Entity::getId).reversed()).collect(Collectors.toList());
}

Entity contains two fields named id of type Integer and size of type BigDecimal and list is a type of List<Entity>.

Since there are several other classes having the same fields with the same datatypes, I want this method to be generic so that it has to be defined only once like so,

public <T extends Object> List<Comparator<T>> sort(Map<String, String> map, Class<T> clazz) {
    List<Comparator<T>> list = new ArrayList<Comparator<T>>();

    // Sorting logic.

    return list;
}

But doing so, expressions like T::getId will not compile as obvious, since the generic type parameter T evaluates to Object.

Is there a way to code sorting without knowing the actual class type so that this method can be prevented from being repeated everywhere, when it is needed?

like image 313
Tiny Avatar asked Jun 04 '26 17:06

Tiny


1 Answers

A simple way, without having to rely on reflection magic, is to introduce a common interface for all the types having the same fields with the same datatypes as Entity.

Consider the following IdSize interface with the following Entity.

interface IdSize {
    Integer getId();
    BigDecimal getSize();
}

class Entity implements IdSize {

    private Integer id;
    private BigDecimal size;
    @Override
    public Integer getId() {
        return id;
    }
    @Override
    public BigDecimal getSize() {
        return size;
    }

}

Then you can make your method generic like this:

public <T extends IdSize> List<Comparator<T>> sort(Map<String, String> map) {
    List<Comparator<T>> list = new ArrayList<Comparator<T>>();
    for (Map.Entry<String, String> entry : map.entrySet()) {
        boolean sortOrder = entry.getValue().equalsIgnoreCase("asc");
        Comparator<T> comparator = null;
        switch (entry.getKey()) {
            case "id":
                comparator = Comparator.comparing(IdSize::getId);
                break;
            case "size":
                comparator = Comparator.comparing(IdSize::getSize);
                break;
            default: // do something here, throw an exception?
        }
        list.add(sortOrder ? comparator : comparator.reversed());
    }
    return list;
}

(I refactored a little the switch-case statement to remove the duplicated code.). Also, you might want to add a default clause.

like image 111
Tunaki Avatar answered Jun 06 '26 06:06

Tunaki