3

I have an 'Animal' interface and 'Dog' class that implements 'Animal'.

public interface Animal {
    void makeVoice();
}
public class Dog implements Animal {
    @Override
    public void makeVoice() {
        System.out.println("aou aou");
    }
}

In my main method, I wrote the following lines.

public class Main {
    public static void main(String[] args) {
        Animal dog = Dog::new; // How is it compiled? it should be Supplier<Animal>

        dog.makeVoice(); // Doesn't call because 'dog' holds lambda expression (Supplier<Animal>) and not the actual 'Dog' class.
    }
} 

Why is it possible that Animal can hold Supplier?

I expected that it doesn't compile.

  • 3
    Your `Animal` interface is a valid functional interface, so you can assign `Dog::new` to a variable of that type, just like if it were `Supplier`, `Runnable` or any other functional interface that doesn't take arguments (because `Dog::new` is "void-compatible", it doesn't matter if it has a return type or void). If you add another method to the interface, it won't be a functional interface anymore. – Mark Rotteveel Jul 14 '23 at 16:14
  • 3
    BTW: the variable `dog` doesn't hold a `Supplier`, it holds an `Animal` (specifically a lambda that will execute `new Dog()` when you call `makeVoice()`). – Mark Rotteveel Jul 14 '23 at 16:17
  • when I call makeVoice(), nothing happens.. – Maor Hassid Jul 14 '23 at 16:33
  • 3
    Create the no argument constructor for Dog and add a print statement. You will see it execute when calling makeVoice. – Unmitigated Jul 14 '23 at 16:34
  • 2
    I think that I understand. 'Animal' has "makeVoice()" that it is feet to "Dog::new" signature, so every time I call "makeVoice()" I actually run "() -> new Dog()". Thanks for all! – Maor Hassid Jul 14 '23 at 16:46
  • 2
    If it helps, imagine you've made some kind of monster animal that, when it opens its mouth, spits out a dog instead of making a sound. – Louis Wasserman Jul 14 '23 at 18:05

1 Answers1

3

The constructor for Dog satisfies the Animal functional interface. The parameter types match as both makeVoice and the default constructor for Dog take no parameters. The constructor call is also void-compatible (it matches the void return type) because a class instance creation expression is an expression statement.

The code is essentially equivalent to the following and it creates an implementation of Animal, not Supplier:

Animal dog = () -> new Dog();
// or equivalently, as the created Dog instance is ignored:
Animal dog = () -> {
    new Dog();
};

As an anonymous class, it would look like this:

Animal dog = new Animal() {
    @Override
    public void makeVoice() {
        new Dog();
    }
};
Unmitigated
  • 76,500
  • 11
  • 62
  • 80
  • dog hold Supplier. please check again. Supplier dogSupplier = () -> new Dog(); – Maor Hassid Jul 14 '23 at 16:26
  • 1
    @MaorHassid No, that's not how it works. You can use the same lambda or method reference to implement multiple different functional interfaces, as long as the signature is compatible. You set the type to Animal, so it isn't a Supplier. – Unmitigated Jul 14 '23 at 16:30
  • 1
    @MaorHassid easy to test, a `Supplier` declares a `get()` method, try `dog.get()` – user16320675 Jul 14 '23 at 16:33
  • How can you explain that 'dog.makeVoice()' doesn't work? – Maor Hassid Jul 14 '23 at 16:40
  • 3
    @MaorHassid when calling `makeVoice()`, from the interface `Animal`¹, it will do `new Dog()`, that is, create an instance of `Dog` which will then be lost, since `makeVoice()` is declared to return `void`... add `System.out.println(...)` to the constructor of `Dog` to verify that. || **¹** it is NOT calling `makeVoice()` from `Dog` – user16320675 Jul 14 '23 at 16:44