Java 8 Lambda: Comparator

I want to sort a list with Lambda:

List<Message> messagesByDeviceType = new ArrayList<Message>();      
messagesByDeviceType.sort((Message o1, Message o2)->o1.getTime()-o2.getTime());

But I got this compilation error:

 Multiple markers at this line
    - Type mismatch: cannot convert from long to int
    - The method sort(Comparator<? super Message>) in the type List<Message> is not applicable for the arguments ((Message o1, Message o2) 
     -> {})

Solution 1:

Comparator#compareTo returns an int; while getTime is obviously long.

It would be nicer written like this:

.sort(Comparator.comparingLong(Message::getTime))

Solution 2:

Lambda

The lambda can be seen as the shorthand of somewhat cumbersome anonymous class:

Java8 version:

Collections.sort(list, (o1, o2) -> o1.getTime() - o2.getTime());

Pre-Java8 version:

    Collections.sort(list, new Comparator<Message>() {
        @Override
        public int compare(Message o1, Message o2) {
            return o1.getTime() - o2.getTime();
        }
    }); 

So, every time you are confused how to write a right lambda, you may try to write a pre-lambda version, and see how it is wrong.

Application

In your specific problem, you can see the compare returns int, where your getTime returns long, which is the source of error.

You may use either method as other answer method, like:

Long.compare(o1.getTime(),o2.getTime())

Notice

  • You should avoid using - in Comparator, which may causes overflow, in some cases, and crash your program.

Solution 3:

The Comparator's compare() method must return an int, and it seems yours is returning a long.

You can change it to:

(Message o1, Message o2)->Long.compare(o1.getTime(),o2.getTime())

This is assuming (based on your error message) that o1.getTime() returns a long.

Solution 4:

Comparator

We use comparator interface to sort homogeneous and heterogeneous elements for default, customized sorting order.

int compare(T o1, T o2);

it takes two arguments for ordering. Returns a

    negative integer(-1) « if first argument is less than the other
    zero             (0) « if both are equal
    positive integer (1) « if first greater than the second.

Anonymous Classes how to sort a list of objects in prior versions of Java 8 using inner Classes.

An anonymous class cannot access local variables in its enclosing scope that are not declared as final or effectively final.

Comparator<Employee> timeCompare = new Comparator<Employee>() {
    @Override public int compare(Employee e1, Employee e2) {
        return e1.getCreationTime().compareTo( e2.getCreationTime() );
    }
};

Java 8 Lambda Expressions uing compare method

A lambda expression is like a method: it provides a list of formal parameters and a body - an expression or block - expressed in terms of those parameters.

LambdaExpression: LambdaParameters -> LambdaBody

Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final, or a compile-time error occurs where the use is attempted.

Comparator<Employee> functional_semantics = (e1, e2) -> {
   return e1.getCreationTime().compareTo( e2.getCreationTime() );
};

Basic Sort with Lambda Support

Comparator<Employee> timeCompareLambda = (o1, o2) -> (int) ( o1.getCreationTime() - o2.getCreationTime());
Collections.sort(java8, timeCompareLambda );

Using Extracted Key and Comparing method: A comparator that compares by an extracted key. Pass references using :: keyword.

static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor)
ToLongFunction<Employee> keyExtracor = Employee::getCreationTime;
Comparator<Employee> byTime = Comparator.comparingLong( Employee::getCreationTime );

Sample Test Code:

public class Lambda_Long_Comparator {
    public static void main(String[] args) {

        List<Employee> java7 = getEmployees();

        // Sort with Inner Class
        Comparator<Employee> timeCompare = new Comparator<Employee>() {
            @Override public int compare(Employee e1, Employee e2) {
                return e1.getCreationTime().compareTo( e2.getCreationTime() );
            }
        };

        // Collections.sort(list); // Defaults to Comparable<T> « @compareTo(o1)
        Collections.sort(java7, timeCompare); // Comparator<T> « @compare (o1,o2)
        System.out.println("Java < 8 \n"+ java7);

        List<Employee> java8 = getEmployees();
        Collections.sort(java8, Comparator
                .comparing( Employee::getCreationTime )
                .thenComparing( Employee::getName ));
        //java8.forEach((emp)-> System.out.println(emp));
        System.out.println("Java 8 \n"+java8);
    }

    static List<Employee> getEmployees() {
        Date date = Calendar.getInstance().getTime();
        List<Employee> list = new ArrayList<Employee>();
        list.add( new Employee(4, "Yash", date.getTime()+7));
        list.add( new Employee(2, "Raju", date.getTime()+1));
        list.add( new Employee(4, "Yas", date.getTime()));
        list.add( new Employee(7, "Sam", date.getTime()-4));
        list.add( new Employee(8, "John", date.getTime()));
        return list;
    }
}
class Employee implements Comparable<Employee> {
    Integer id;
    String name;
    Long creationTime;

    public Employee(Integer id, String name, Long creationTime) {
        this.id = id;
        this.name = name;
        this.creationTime = creationTime;
    }

    @Override public int compareTo(Employee e) {
        return this.id.compareTo(e.id);
    }

    @Override public String toString() {
        return "\n["+this.id+","+this.name+","+this.creationTime+"]";
    }

    // Other getter and setter methods
}

See these posts also:

  • Java 8 Tutorial
  • Java8 Lambdas vs Anonymous classes
  • Lambda vs anonymous inner class performance

Solution 5:

You should change

 messagesByDeviceType.sort(
     (Message o1, Message o2) -> o1.getTime() - o2.getTime()
 );

to

messagesByDeviceType.sort(
    Comparator.comparing((Message m) -> m.getTime())
);

That assumes that the value is Comparable, which provides a natural sort order.

If you want to add more fields then you can can chain them to the Comparator. e.g. to sort first by time, and then by sender:

messagesByDeviceType.sort(
    Comparator
        .comparing((Message m) -> m.getTime())
        .thenComparing((m)     -> m.getSender())
);

To reverse the order of any Comparator, chain the reveresed() method to it, e.g. to sort first by time descending, and then by sender:

messagesByDeviceType.sort(
    Comparator
        .comparing((Message m) -> m.getTime())
        .reversed()
        .thenComparing((m)     -> m.getSender())
);

See also https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html