Search code examples
javamethodslambdacomparablemethod-reference

How to use a Function<T, R> as parameter in method


I'm learning Java lambdas for school, and I am stuck for a couple of days now.

Background

I have a list of pumps which I have to sort out on power, last revision, … I already wrote a Comparator that's returning a List<Pump>:

class PowerComparator implements Comparator<Pomp> {
    @Override
    public int compare(Pump pump1 , Pump pump2) {
        return Double.compare(pump1.getPower(), pump2.getPower());
    }
}

I have to write one function that's returning a List<Pump> that can returning a sorted list (power, revision, ...) using a lambda.

The method signature is:

public List<Pump> sortedBy(Function<Pump, Comparable<Pump>> function)

I know that the Function interface is returning a Comparator, but I don't know how to use the function in it.

Below is what I already found (it's not correct). I am really stuck here.

public List<Pump> sortedBy(Function<Pump, Comparable<Pump>> function){
    List<Pump> sortBy = new ArrayList<Pump>(pumps);
    function.apply((Pump) ->Comparator.comparing(pumps::comparator));
    Collections.sort(sortBy, Comparator.comparing(function.apply(pumps);
    return sortBy;
}

Additional info (in dutch)

public class Data {

    private static List<Pomp> data;

    public static List<Pomp> getData() {
        data = new ArrayList<>();

        data.add(new Pomp("J6706A", 100.0, 2, Aandrijving.TURBINE, LocalDate.of(2022, 1, 10), true, 500.0, "Slurry pomp"));
        data.add(new Pomp("J6707A", 55.5, 1, Aandrijving.MOTOR, LocalDate.of(2022, 2, 10), false, 500.0, "Clarified pomp"));
        data.add(new Pomp("J6706B", 100.0, 2, Aandrijving.TURBINE, LocalDate.of(2022, 3, 10), true, 500.0, "Slurry pomp"));
        data.add(new Pomp("J6706C", 100.0, 2, Aandrijving.TURBINE, LocalDate.of(2022, 4, 10), true, 500.0, "Slurry pomp"));
        data.add(new Pomp("J6705A", 62, 1, Aandrijving.MOTOR, LocalDate.of(2022, 5, 10), false, 250, "Voedings pomp"));
        data.add(new Pomp("J6705B", 35, 2, Aandrijving.TURBINE, LocalDate.of(2022, 6, 10), false, 150, "Voedings pomp"));
        data.add(new Pomp("J6708B", 100.0, 2, Aandrijving.TURBINE, LocalDate.of(2022, 7, 10), false, 300, "HCO circ pomp"));

        return data;
    }

}

public class Pompen {
    private TreeSet<Pomp> pompen = new TreeSet<>();
    public void add(Pomp pomp) {
        pompen.add(pomp);
    }
 class VermogenComparator implements Comparator<Pomp> {
        @Override
        public int compare(Pomp pomp1 , Pomp pomp2) {
            return Double.compare(pomp1.getVermogen(), pomp2.getVermogen());
        }
    }
    class RevisieComparator implements Comparator<Pomp> {
        @Override
        public int compare(Pomp pomp1 , Pomp pomp2) {
            return pomp1.getLaatsteRevisie().compareTo(pomp2.getLaatsteRevisie());
        }
    }
    class Zelfontbranding implements Comparator<Pomp> {
        @Override
        public int compare(Pomp pomp1 , Pomp pomp2) {
            return Boolean.compare(pomp1.getBovenZelfOntbranding(), pomp2.getBovenZelfOntbranding());
        }
    }

Solution

  • Because of your example using sortBy(Pump::getName), I believe that the intent here is to use the Comparator.comparing() factory to create a comparator that extracts a sort key from each object. However, this requires some changes to the generic types used in the prescribed method signature. Working code would look something like this:

    public <U extends Comparable<? super U>> List<Pomp> sortedBy(Function<Pomp, ? extends U> toKey) {
        List<Pomp> sorted = new ArrayList<>(pompen);
        sorted.sort(Comparator.comparing(toKey));
        return sorted;
    }
    

    This will accept lambdas like Pomp::getNaam as long as the indicated property is Comparable:

    System.out.println("Pumps sorted on power:");
    pompen.sortedBy(Pomp::getVermogen).forEach(System.out::println);
    

    A better design would be to pass a Comparator; while it's a tiny bit more work for the caller, it gives them full control over the sorting. For example, they can specify a secondary sort key, or reverse the order. Or one could go another step further and simply return a copy of the pumps collection as a list and let the caller do whatever they wish with it.


    If permitted, you could change the API to this:

    public List<Pomp> sortedBy(Comparator<? super Pomp> order) {
        List<Pomp> sorted = new ArrayList<>(pompen);
        sorted.sort(order);
        return sorted;
    }
    

    The caller would then be responsible for creating a Comparator that meets their need:

    /* Like this: */
    List<Pomp> sortedByName = pompen.sortedBy(Comparator.comparing(Pomp::getNaam));
    /* Or this: */
    List<Pomp> pumpsDescendingPower = 
      pompen.sortedBy(Comparator.comparing(Pomp::getVermogen).reversed());
    

    This approach is more idiomatic for Java.