1
import java.util.*;

class TreeMapDemo
{
    public static void main(String args[])
    {
        Comparator <String> c1 = (str1, str2) -> 0;

        Comparator <String> c2 = (str1, str2) -> 1;

        TreeMap <String, Double> tm1 = new TreeMap(c1.thenComparing(c2));
        //Working fine

        TreeMap <String, Double> tm2 = new TreeMap(((str1, str2) -> 0).thenComparing((str1, str2) -> 1));
        //Error: Lambda expression not expected here
        //<none> can not be dereferenced
    }
}

My query is:

If

c1 = (str1, str2) -> 0 and c2 = (str1, str2) -> 1,

Then why

c1.thenComparing(c2) is working fine and

((str1, str2) -> 0).thenComparing((str1, str2) -> 1) is not?

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
kevin gomes
  • 1,775
  • 5
  • 22
  • 30
  • Lambda expression is not a comparator. `thenComparing` is a Comparator method. – Nir Alfasi Sep 13 '15 at 23:09
  • @alfasin : if it is not a comparator, then how it is mapped with the TreeMap constructor – kevin gomes Sep 13 '15 at 23:13
  • It's not, the compilation error you're (supposed to be) getting is: "lambda expression not expected here". You probably already figured out that you *can* cast it to a comparator and it will compile just fine (ugh...) – Nir Alfasi Sep 13 '15 at 23:17
  • Along with `lambda expression not expected here`, i am getting ` can not be dereferenced` – kevin gomes Sep 13 '15 at 23:18
  • Not only I'm not getting the second error, by casting the first lambda expression to a comparator - it compiles and runs fine! (including *put*ting items into the tree later on). – Nir Alfasi Sep 13 '15 at 23:21
  • @alfasin :But why casting is needed here, the lambda expression should be of the target type `comparator`, because of `TreeMap` constructor – kevin gomes Sep 13 '15 at 23:25
  • 3
    Because it's the whole expression that must have type Comparator, not the object on which `thenComparing` is invoked. If I extend `BiFunction` and add a default method `thenComparing` that returns a `Comparator` it becomes ambiguous. – Paul Boddington Sep 13 '15 at 23:29
  • 3
    Aside from whatever error you're getting, a `Comparator` that always returns 1 cannot be right. Comparators must be symmetric, which means that if `c.compare(x,y)` returns 1, then `c.compare(y,x)` must return something negative. Also, `c.compare(x,x)` must return 0. If you use a `Comparator` that violates those rules, and you try to use it in a `sort` or something, the algorithm will lose its mind. – ajb Sep 13 '15 at 23:53
  • @ajb :You are absolutely correct. In the above example, what the comparator return is no matter for me. My actual program was something else, I just modifed my program particularly for my query. – kevin gomes Sep 14 '15 at 05:59

3 Answers3

7

Java relies on the context of a lambda expression to determine its type. Assigning a lambda to a variable or passing it as a method argument provides sufficient context, but invoking a method -- such as thenComparing() -- on a lambda provides no useful context at all.

This should work:

Comparator <String> c1 = (str1, str2) -> 0;
TreeMap <String, Double> tm2 = new TreeMap(c1.thenComparing((str1, str2) -> 1));

In more detail:

A lambda expression evaluates to an object of a type that implements some functional interface, and Java relies on the context in which the expression appears to determine which functional interface that is. In your second case, the constructor argument is not the lambda's context; the invocation of thenComparing() is. It is that method's return value that is the constructor argument. But before Java can determine anything about thenComparing() it needs to know the type of the object on which it is being invoked. The type informs about the method, not the other way around.

Java cannot work backwards from the argument type to the needed type of the lambda because there could be any number of functional interfaces that would work. Java cannot assume that the wanted interface is in the standard library.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • A little more explanation will be greatly appreciated(mainly `context`) – kevin gomes Sep 13 '15 at 23:11
  • @kevingomes, more detail, as requested. If there are specific points that are still unclear then feel free to ask for specific clarification. – John Bollinger Sep 13 '15 at 23:37
  • You may want to use the term "standalone expression" to refer to the `x` in any `x.someMethod()`. When resolving a standalone expression the compiler has not "target type" and hence no functional interface type can be determined for a lambda in this position. – Stephan Herrmann Sep 15 '15 at 14:31
4

Lambda expressions have to target a particular type.

c1 = (str1, str2) -> 0;

is OK because the type of c1 is known.

On its own (str1, str2) -> 0 is ambiguous.

For example

BiFunction<String, String, Integer> x = (str1, str2) -> 0;

makes sense too.

To make this concrete, look at this example.

public interface Foo extends BiFunction<String, String, Integer> {
    default Comparator<String> thenComparing(Comparator<String> comparator) {
        return String.CASE_INSENSITIVE_ORDER;
    }
}

public static void main(String[] args) {
    Foo foo = (str1, str2) -> 0;        // overriding the sole method of BiFunction
    TreeMap<String, Double> treeMap = new TreeMap<>(foo.thenComparing((str1, str2) -> 1));
}

This compiles perfectly ok. So if we just write

new TreeMap<>((str1, str2) -> 0).thenComparing((str1, str2) -> 1));

there's no way for the compiler to know whether (str1, str2) -> 0 is a Foo or a Comparator<String>. The inference does not work backwards in this manner.

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • `(str1, str2) -> 0` is targeting to the type `Comparator`, because of `TreeMap` constructor – kevin gomes Sep 13 '15 at 23:16
  • 2
    But you're expecting very sophisticated rules here. There could be other classes with a `thenComparing` method that returns a `Comparator`. You're expecting Java to start with a Comparator and then look backwards through all classes and methods that could return a Comparator to deduce the type of the original lambda expression. – Paul Boddington Sep 13 '15 at 23:20
0

Thats a limitation of type inference in Java. First of all you using a raw-type and the comparator will be resolved to a Comparator and not Comparator<String>

TreeMap <String, Double> tm1 = new TreeMap((str1, str2) -> 0);// wouldn't work

where

TreeMap <String, Double> tm1 = new TreeMap<>((str1, str2) -> 0);// works

Now, You can't call a specific method on a type before it gets inferred.

Comparator<String> c1 = ((str1, str2) -> 0).thenComparing((str1, str2) -> 1); // doesn't work

Unless you provide some additional help to the compiler

Comparator <String> c1 = ((Comparator<String>)((str1, str2) -> 0)).thenComparing((str1, str2) -> 1); // works
Sleiman Jneidi
  • 22,907
  • 14
  • 56
  • 77