Why doesn't it output 'dog eat'

123 views Asked by At

code like this

public class LambdaTest {
    public static void main(String[] args) {
        final Animal animal = Dog::new;
        animal.eat();
    }
}

@FunctionalInterface
interface Animal {
    void eat();
}

class Dog implements Animal {

    public Dog() {
        System.out.println("dog init.");
    }

    @Override
    public void eat() {
        System.out.println("dog eat");
    }

When I ran this code, "dog init." was printed to the console, but "dog eat" was not. Why is that? Can someone tell me the reason?

I expected "dog init" and "dog eat" to be printed, but only "dog init" was printed. Additionally, I'm puzzled as to why there was no error when Animal animal = Dog::new;.

2

There are 2 answers

5
knittl On BEST ANSWER

Animal is a functional interface with a single method void eat() (void return, no parameters) – also known as SAM type (single abstract method).

It is structurally identical to the java.util.Runnable interface (interface Runnable { void run(); }). Your code could as well be written as:

final Runnable runnable = Dog::new;
runnable.run(); // calls the constructor

You can assign it any lambda or method reference with zero arguments and no return value, e.g. () -> {}.

Dog::new is a method reference and equivalent to the lambda () -> { new Dog(); }. If you look closely, you will notice that this lambda does not return anything. It constructs a new Dog instance and then forgets about it again.

Assigning a method reference or a lambda to a variable does not execute it (yet). You have to explicitly invoke the lambda by calling the named method from your interface.

Now, your animal is an instance of your Animal interface and got assigned a method reference which can then be invoked later. animal.eat() invokes the assigned method reference, calling the constructor.

If you want your variable animal to hold a Dog instance and then invoke the eat method on it, call the constructor directly: Animal animal = new Dog();

The "problem" is the signature of your eat method, because it is equivalent to Runnable#run and allows assigning any "empty" action.

Lambdas and method references were introduced with Java 8. In Java versions before, you had to create an anonymous class to achieve the same behavior:

final Animal animal = new Animal() {
  @Override public void eat() {
    new Dog();
  }
};
animal.eat(); // calls "eat" of the anonymous instance, which in turn calls the constructor; does NOT call `Dog#eat`
0
Sweeper On

Animal is an interface with a single abstract method, eat. This method does not take any parameters and does not return anything. Because of this, it can represent any function that takes no parameters. For example:

Animal foo = () -> System.out.println("foo");

Now if I call foo.eat(), it will run the code in the lambda, which prints "foo".

I can also use a method reference instead of a lambda:

Animal foo = SomeClass::someStaticMethod;

Now foo.eat() will run the parameterless method called someStaticMethod located in SomeClass.

Notice that this works in exactly the same way as using the built-in Runnable interface and called its run method. Yours is just called Animal and eat, rather than Runnable and run.

So by the same logic, using a constructor reference of Dog will invoke the constructor of Dog.

Animal foo = Dog::new;
Runnable bar = Dog::new;

// these two lines do the same thing:
foo.eat();
bar.run();

Would you still have the confusion if you have written the code with Runnable instead?

Note that the fact that Dog implements Animal does not matter here. Dog implements Animal just means that you can do:

Animal animal = new Dog();

which has a completely different meaning. If at this point I do animal.eat(), then it will output what you expect - "dog eat".

Alternatively, you could also make a method reference to a newly-created Dog's eat method for it to output "dog eat":

Animal animal = new Dog()::eat;