11

In the book OCP Study Guide there is this example about a Comparator that can be initialized in two ways. The first is via an anonymous class like this:

Comparator<Duck> byWeight = new Comparator<Duck>(){
    public int compare(Duck d1, Duck d2){
        return d1.getWeight() - d2.getWeight();
    }
};

This I can understand. According to the book this can be replaced with a lambda expression like this:

Comparator<Duck> byWeight = (d1,d2) -> d1.getWeight() - d2.getWeight();

Now this I don't understand. The lambda expression does not return a Comparator object, which it couldn't now that I think of it since Comparator is an interface.

So does the new operator in the first example refer to the anonymous class that is being made which is called Comparator because that anonymous class implements the Comparator interface?

What exactly is happening in example 2 then? Is an object created somehow out of the lambda expression? In this example you use byWeight as a reference variable right?

I really don't understand this, could anyone please explain? Thank you.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Maurice
  • 6,698
  • 9
  • 47
  • 104
  • "The lambda expression does not return a comparator [sic] object, which it coulden;t [sic] now that I think of it since comparator [sic] is an interface." Actually, it doesn't return anything, it implicitly creates a `Comparator` object, which it can because it creates an instance of an anonymous implementing class. You can create an object of any interface type by creating an object of a class type that implements the interface. – Lew Bloch May 09 '17 at 09:40
  • 3
    @LewBloch Don't be a [dic] – Michael May 09 '17 at 09:43
  • 4
    The lambda expression *does* return an instance of Comparator object. The first and second code is much the same in many ways. The second is just shorter. – Peter Lawrey May 09 '17 at 09:55
  • 3
    As a side note, you should actually avoid implementing comparators by subtracting values. It’s tempting to do so, but an anti-pattern that will lead to overflow bugs. It will work if the values are all-positive, but even if that might be true in one scenario, you will sooner or later use it in other situations, once it became a habit. And then, you’ll become a victim of hard-to-reproduce bugs, as it overflows only occasionally. Use either, `(d1,d2) -> Integer.compare(d1.getWeight(), d2.getWeight())` or even better `Comparator.comparingInt( d -> d.getWeight() )`. – Holger May 09 '17 at 15:21

9 Answers9

16

If you read the documentation of the Comparator interface, you can read:

Functional Interface: This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

The Comparator<T> interface is thus implemented like:

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);

    // ...

}

Now if we look at the documentation of @FunctionalInterface we see:

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification. Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

So basically if you have an interface with one abstract method, and you annotate the interface as a @FunctionalInterface, then that interface is a target for functions: in that you more or less construct an anonymous class that implements the functional interface and the function you specify is the implementation of the only abstract method.

In other words, the expression:

Comparator<Duck> byWeight = <somelambda>

is equivalent to:

Comparator<Duck> byWeight = new Comparator<Duck>(){
    public int compare(Duck d1, Duck d2){
        return <somelambda>(d1,d2);
    }
}
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • Possibly also relevant java lang spec for functional interface: https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.8 – dnsmkl Aug 07 '18 at 08:52
3

In the first code block the created object instance implements Comparator<Duck>, but the corresponding class has no name (is anonymous).

In the second code block, the same thing happens. Because the Comparator interface only defines a single method (named compare), it is possible to abbreviate the creation of an (anonymous) implementation of the interface using a lambda expression.

The variable byWeight can be used the same way in both examples. Everywhere a Comparator<Duck> is required, byWeight can be used - which corresponds to the type information of the variable. Internally, whenever compare is invoked on this implementation, the definition provided using the lambda expression is used.

C-Otto
  • 5,615
  • 3
  • 29
  • 62
  • and because the class has no name I don't have to use the keyword implements either? About the variable byWeight, it is actually referencing to an anonymous class (object) that implements Comparator then. Right? – Maurice May 09 '17 at 09:42
  • 2
    The "new INTERFACE{}" expression in Java already mentions the interface you implement, so there's no need to duplicate this information. The variable contains a reference to an object instance, no matter where that object comes from or how the corresponding class is defined. There's no way to define the variable type to be more specific than `Comparator` as there is the implementing class has no name. In short: right! – C-Otto May 09 '17 at 09:46
  • 2
    @Maurice You can imagine it that way and it will do you no harm. In practice, no. There are disadvantages to using anonymous classes and so they're no implemented this way. [Read this article if you are interested](https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood) but it may be a bit overwhelming. – Michael May 09 '17 at 09:47
2

In Java 8 Comparator<T> is annotated with @FunctionalInterface. It's documentation says:

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification. Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

If a type is annotated with this annotation type, compilers are required to generate an error message unless:

The type is an interface type and not an annotation type, enum, or class. The annotated type satisfies the requirements of a functional interface. However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

The most important part here is that instances of functional interfaces can be created with lambda expressions, method references, or constructor references., which answers your question.

Piotrek
  • 79
  • 3
2

A comparator is basically just a function which takes two parameters and returns an int.

Effectively what's happening here is that the compiler is able to cleverly infer what the right-hand side needs to be because of how you declared the left-hand side.

Comparator<Duck> byWeight = (d1,d2) -> d1.getWeight() - d2.getWeight();
                           //^   ^ I know a Comparator<Duck> takes two Ducks.
                                      // ^ I know a Comparator<Duck> returns an int

This is all possible because a Comparator<T> is defined as a functional interface:

This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

Community
  • 1
  • 1
Michael
  • 41,989
  • 11
  • 82
  • 128
  • ok so what happens is that the compiler searches in the interface Comparator for a method that has 2 parameters (because I indicated 2 parameters (d1,d2) on the left side of the -> operator and then it finds that method, but it has no implementation yet. The implementation is provided on the right side of the -> operator, am I understanding this correctly? – Maurice May 09 '17 at 09:47
  • A functional interface is only allowed to contain one method so there is no need to search. It either matches or it doesn't. But otherwise, yep. – Michael May 09 '17 at 09:50
2

The interface Comparator is a Functional Interface, this means this interface can only contains one abstract method.

Then you can use lambda expression to define the implementation of this abstract method, basically (d1,d2) -> d1.getWeight() - d2.getWeight(); is the implementation of the abstract method int compare(T o1, T o2);.

As a functionnal Interface contains only one abstract method, you can use lambda expression to define the implementation of such interface

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
Olivier Boissé
  • 15,834
  • 6
  • 38
  • 56
1

In the first way you're creating a new anonymous class (that has a method with a behaviour).

In the second way you're just exposing the behaviour (consider this like a way to share a function, a method, without see the surrounding class even if it is created transparently).

I remember was explained clearly in the Java Tutorial - The Lambda Expressions

One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear. In these cases, you're usually trying to pass functionality as an argument to another method, such as what action should be taken when someone clicks a button. Lambda expressions enable you to do this, to treat functionality as method argument, or code as data.

I suggest to focus your mind that, using lambdas, you're trying just to expose a behaviour to some class or component, your example may be to the Collections.sort .

The lambdas give you the opportunity to have a more clear and simple expression avoiding the boilerplate of the anonymous class declarations.

freedev
  • 25,946
  • 8
  • 108
  • 125
  • In the second way you are also creating an instance of an anonymous class. The two idioms are semantically​ equivalent. – Lew Bloch May 09 '17 at 09:42
  • @LewBloch yes, but this is done transparently to the "user", right? – freedev May 09 '17 at 09:43
  • Given that the user is a programmer, no. It is up to us to understand what's going on. – Lew Bloch May 09 '17 at 09:45
  • yes, understand but also focus on what is more important. Anyway I've updated my answer in order to reflect your comment. hope this helps. – freedev May 09 '17 at 09:50
  • 2
    @LewBloch: "In the second way you are also creating an instance of an anonymous class." The Java standard doesn't say anything about what is the class of the object that a lambda evaluates to, whether it's an anonymous class or some other class; and it doesn't say that evaluating a lambda necessarily "creates" an instance, or whether it can return an existing instance. – newacct May 10 '17 at 01:19
  • @newacct, I was just going by the statement in the JLS, "Evaluation of a lambda expression produces an instance of a functional interface (§9.8)." Since an interface cannot be directly instantiated, it must be an instance of an implementing class. Since the lambda expression doesn't name the implementing class, it must be an anonymous class. Since the instance is not explicitly coded, it must be created somehow by dint of being from a lambda expression. Q.E.D. – Lew Bloch May 10 '17 at 07:24
  • 1
    @LewBloch: "produces" doesn't mean "creates". "Create" implies a new instance, whereas "produce" doesn't have to be a new one. A `new` expression to create an instance of an anonymous class is guaranteed to be a new and distinct instance (different identity (`!=`) from any other instance that you may have). However, when you have a lambda expression that doesn't capture any variables, in the Sun Java implementation, multiple executions of the same lambda expression will produce the same instance (can be verified with `==`). – newacct May 17 '17 at 07:48
1

Basically lambdas behave very much like inner classes but allow for a nicer syntax to be used with less overhead.

With Java8 Comparator is a @FunctionalInterface (JavaDoc) which makes it usable with lambda expressions. That is you can define a Bi-Function (function with two arguments) with return type int and use it as Comparator, which is done in your second example. Both Comparator instances can be used in exactly the same way in your remaining code.

dpr
  • 10,591
  • 3
  • 41
  • 71
  • In what sense is there "less overhead"? – Lew Bloch May 09 '17 at 09:40
  • @Lew Bloch cluttered source code. From a functional point of view there is no difference in overhead using the lamdba version or the inner class. – dpr May 09 '17 at 09:49
1

Lambda expression is just a short-hand for a functional interface (an interface with just one function), you don't need to write new/function name just write parameter list in ( yourParameterListHere ) and then -> and after this write what to do/return (i.e function body). you can also write it with { } like

Comparator<Duck> byWeight = (d1,d2) -> { d1.getWeight() - d2.getWeight(); }
S Jayesh
  • 191
  • 1
  • 4
  • 19
1

You can think of a lambda as a method that doesn't belong to a class, that can be passed around like a piece of data.

Or, with a very small mental shift, you can think of it as an object with only one method.

There is a set of interfaces marked as functional interfaces. This is mentioned in the Javadoc as:

Functional Interface:

This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

That's a technical way of saying that because they have only one method, and they're marked as functional, the compiler can treat a lambda as an object with that interface. It knows to use the lambda as the implementation of the one method in the interface.

So you can do:

Comparator<Pet> byNameComparator = (f1,f2) -> f1.name().compareTo(f2.name());
Predicate<Customer> isVip = cust -> cust.orderCount > 20;
Callable<Item> getResult = () -> queue.getItem();
Function<Integer,Integer> multiply = (a,b) -> a * b;

... and so on.

And wherever a parameter's type is a functional interface, you can instead use a lambda directly, so given:

 public void sort(Comparator cmp);

... you can call it as:

 foo.sort( (a,b) -> a.name().compareTo(b.name()));
Community
  • 1
  • 1
slim
  • 40,215
  • 13
  • 94
  • 127