25

I am learning lambda expressions and functional interfaces. We can directly write an implementation of the interface by the lambda expression. So I think, it could be the alternative for polymorphism.

I have some code using polymorphism,

interface Drawable {
    public void draw();
}


class Shape {

    protected String name;

    public Shape(String name) {
        this.name = name;
    }
}

class Rectangle extends Shape implements Drawable  {

    public Rectangle(String name) {
        super(name);
    }

    @Override
    public void draw() {
        System.out.println("I am "+this.name);
        System.out.println("Drawing rectangle with 2 equal sides.");
    }
}

class Square extends Shape implements Drawable {

    public Square(String name) {
        super(name);
    }

    @Override
    public void draw() {
        System.out.println("I am "+this.name);
        System.out.println("Drawing square with 4 equal sides.");
    }
}


public class DrawShape {

    public static void main(String ar[]) {

        Drawable rectangle = new Rectangle("Rectangle");
        rectangle.draw();

        Drawable square = new Square("Square");
        square.draw();

    }
}

I have written above code using lambda expressions and functional interface,

@FunctionalInterface
interface Drawable {
    public void draw();
}


class Shape {
    private String name;
    public Shape(String name) {
        this.name = name;
    }

    public void draw(Drawable d1) {
        System.out.println("I am "+this.name);
        d1.draw();
    }
}



public class DrawShape {

    public static void main(String[] args) {
        Shape s1 = new Shape("Rectangle");
        Drawable rectangle = () -> System.out.println("Drawing rectangle with 2 equal sides.");
        s1.draw(rectangle);

        Shape s2 = new Shape("Square");
        Drawable sqaure = () -> System.out.println("Drawing square with 4 equal sides.");
        s2.draw(sqaure);
    }

}

Which is the better approach? What about other aspects like code reusability, code maintenance and modification, coupling and cohesion etc for lambda?

Kaustubh Khare
  • 3,280
  • 2
  • 32
  • 48
  • 4
    Not every polymorphism example can be written this simply, though, so I feel like you're generalizing here. This is just deferring an action from a subclass to an anonymous one – OneCricketeer Jul 31 '18 at 04:59
  • @cricket_007 Yes. you are right, but if we try to simplify object creation it's draw implementation using factory or another design pattern? – Kaustubh Khare Jul 31 '18 at 05:12
  • Your second implementation, I don't think it is polymorphism, it's a normal function call. In general case, rectangle and square should have their own properties, you need two classes to represent these two types. What if you want two rectangle objects? Lambda is just a simple way to write anonymous inner class. – zhh Jul 31 '18 at 05:18
  • lambda expression should be used for functionality like anonymous inner class because it does like a function and doesn't need a name. In your example, Square, Shape is real object so declare new class will be preferable – You're awesome Jul 31 '18 at 05:19
  • They can’t be used “as an alternative to polymorphism”, in fact this doesn’t make any sense. But they can be used to *achieve* polymorphism. You seem to confuse polymorphism with one specific way of implementing it, namely subtype based polymorphism. What’s more, this is OOP in its original form, before Java misappropriated the term. – Konrad Rudolph Jul 31 '18 at 10:29
  • 1
    Alternative to *what*? – Holger Jul 31 '18 at 10:55
  • @Holder I was learning lambda expressions and we can write the implementation of the method. So I have tried the above code and it worked. I got confused with it. If it is an alternative then what about code reusability, code maintenance and modification, code coupling and cohesion with lambdas. – Kaustubh Khare Jul 31 '18 at 12:03
  • 2
    Why should anyone be concerned with “code reusability, code maintenance and modification, code coupling and cohesion” for a single line of code (the typical size of a lambda expression)? What does “code coupling and cohesion” in this context mean? Do you even understand these terms or are you just dropping buzzwords? – Holger Jul 31 '18 at 12:25
  • @Holder above code is just an example. If my assumption were true, then these buzzwords are very important in terms of coding. But my assumption is not true. Lambdas cannot be the alternative for polymorphism. – Kaustubh Khare Aug 01 '18 at 04:11

5 Answers5

12

I would argue that lambda expressions allow developers to write fully polymorphic types, the way full class implementations do.

Polymorphism is often seen in two ways:

Drawable drawable = new Rectangle("name");
drawable.draw();
Shape shape = (Shape) drawable; //same object, multiple types.

And:

Drawable drawable2 = new Rectangle("name");
drawable2.draw(); //Rectangle.draw() implementation invoked
drawable2 = new Square("name");
drawable2.draw(); //Square.draw() implementation

Neither of these two is perfectly allowed for by lambda expressions:

  1. Lambda expressions will only be used to implement functional interfaces. This is the first major limitation.
  2. Although one can do this:

    Drawable drawable = () -> System.out.println("drawing rectangle");
    drawable = () -> System.out.println("drawing square");
    

    This is not strictly the same thing as the second code snippet above (in a more complex example, one would be able to provide a basic implementation in Shape, and have it overridden in Rectangle and Square; and that wouldn't be possible with lambdas). Also, one would be correct to argue that the two assignments above use different source code.

  3. One can't just "cast" types as with classes:

    Drawable drawable3 = () -> System.out.println("Drawing something");
    Shape shape3 = (Shape) drawable3; //Class cast exception.
    

In other words, lambda expressions are a good fit for functional programming coding, not a substitute for good Object-Oriented design.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • 3
    “*The class version uses dynamic binding, whereas the lambda expressions depend on the compiler.*”. This sentence makes no sense. Regardless of how the compiler implements them, the method call to `draw()` will use dynamic binding, and it will be exactly the same kind of dynamic binding, if `Drawable` is an interface, as for calling an interface method, it is entirely irrelevant whether the implementation is an ordinary class or generated for a lambda expression. – Holger Jul 31 '18 at 10:59
  • @Holger Thanks for the remark. I suppose the point I want to make is that lambda expressions don't give the same possibilities as classes when it comes to virtual method invocations. This interface/implementation relationship makes my point less important, but polymorphism extends to classes as well. – ernest_k Jul 31 '18 at 11:25
3

I am agree with @yelliver & @cricket_007, what you did is just using anonymous class instead of subclass.

You wrote

Shape s1 = new Shape("Rectangle");
Drawable rectangle = () -> System.out.println("Drawing rectangle with 2 equal sides.");
s1.draw(rectangle);

It is same like

Shape s1 = new Shape("Rectangle");
Drawable rectangle = new Drawable() {
    @Override
    public void draw() {
        System.out.println("Drawing rectangle with 2 equal sides.");
    }
};
s1.draw(rectangle);

So the two examples you created are not same. Your first example uses a subclass whereas the second one is using an anonymous class.

So I think, it could be the alternative for polymorphism

Clearly it is not an alternative, it is just another way to implement.

Benefits of lambda expression in Java.

1. Fewer Lines of Code

In above example you can see anonymous class is changed by just ->.

2. Sequential and Parallel Execution Support by passing behavior in methods

You can iterate any collection by just using foreach(). (example)

list.forEach(item->System.out.println(item));

3. Lambda Expression and Objects

In Java, any lambda expression is an object as is an instance of a functional interface. We can assign a lambda expression to any variable and pass it like any other object (example) . like

list.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());
Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
Khemraj Sharma
  • 57,232
  • 27
  • 203
  • 212
2

lambda-expressions in Java 8 represents the instance of a functional interface (interface with only 1 method). In your 2nd approach, you just create a anonymous class. It does not make sense if you want to re-use code. For more information, you can refer to why/when to use anonymous class: How are Anonymous (inner) classes used in Java?

public static void main(String[] args) {
    Shape s1 = new Shape("Rectangle");
    Drawable rectangle = new Drawable() {
        @Override
        public void draw() {
            System.out.println("Drawing rectangle with 2 equal sides.");
        }
    };
    s1.draw(rectangle);

    Shape s2 = new Shape("Square");
    Drawable sqaure = new Drawable() {
        @Override
        public void draw() {
            System.out.println("Drawing square with 4 equal sides.");
        }
    };
    s2.draw(sqaure);
}
yelliver
  • 5,648
  • 5
  • 34
  • 65
  • No, a lambda expression is not an interface with 1 method, that's called a functional interface. – Coder-Man Jul 31 '18 at 05:00
  • 1
    @Wow lambda-expressions in Java 8 represents the instance of a functional interface – yelliver Jul 31 '18 at 09:22
  • 3
    …an interface with one `abstract` method which does not match a `public` method of `java.lang.Object`. Look at the [`Comparator`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html) interface. It has lots of methods. After sorting out the `default` and `static` methods, there are still two `abtract` methods left, but one matches a `public` method of `java.lang.Object`, so `Comparator` is a *functional interface*… – Holger Jul 31 '18 at 11:04
2

Both the imperative style and the functional style has its own pros and cons. in this specific case using the functional style approach is better simply because you don't need to create a class for every case where you needed to encapsulate a single piece of functionality.

As one can quickly notice creating a class for every case where you needed to encapsulate a single piece of functionality leads to unnecessary boilerplate code

With the imperative approach, one could write code and make it up as you go. one may even end up writing classes upon classes without fully understanding what the implementation will be which may result in a large and unsustainable code base.

On the other hand, the functional approach forces one to better understand their implementation before & while we're coding.

That said, it’s very important to understand that functional programming itself should not be thought of as a "replacement" for object-oriented programming.

You may actually find your self-going back to the OOP approach when the business logic gets more complex.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
1

In my opinion, the second approach is more flexible as the logic of drawing is separated from the shapes themselves.

So, let us concentrate on the second approach. The @FunctionalInterface is optional as any given interface is considered a functional interface if it has only one single abstract method (also called SAM interfaces).

As I said, the second approach is more favorable as any given approach promoting code separation leads to a reusable, maintainable and loosely-coupled solution.

Your second solution is more inclined to a behavioral-passing approach. One way of implementing this approach (specially if your behavior can be represented as a tiny single expression) is by lambda expressions.

So, for example, given a rectangle shape, drawn depending on a given context, you can create two separate lambda expressions (behaviors) and pass them to the drawing logic when the context changes.

    Shape s1 = new Shape("Rectangle");

    s1.draw(() -> System.out.println("Drawing rectangle in context 1."));

    // context changes

    s1.draw(() -> System.out.println("Drawing rectangle in context 2."));

You can not try the code snippet above in your first example, because the drawing logic is hardcoded in the shapes themselves.

To sum up, always favor decoupled solutions, and if your behavior to be decoupled can be represented as tiny single expressions, the best way to approach it is by using lambda expressions or if your behavior is already implemented elsewhere, you can take advantage of method references.

marsouf
  • 1,107
  • 8
  • 15