Могут ли лямбда-выражения быть альтернативой полиморфизму?

Я изучаю лямбда-выражения и функциональные интерфейсы. Мы можем непосредственно написать реализацию интерфейса с помощью выражения лямбда. Поэтому я думаю, что это может быть альтернативой полиморфизму.

У меня есть код, использующий полиморфизм,

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();

    }
}

Я написал выше код, используя лямбда-выражения и функциональный интерфейс,

@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);
    }

}

Какой подход лучше? Как насчет других аспектов, таких как повторное использование кода, обслуживание и модификация кода, сцепление и сцепление и т.д. Для лямбда?

Ответ 1

Я бы сказал, что лямбда-выражения позволяют разработчикам писать полностью полиморфные типы, как это делают реализации полного класса.

Полиморфизм часто встречается двумя способами:

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

А также:

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

Ни один из этих двух вариантов не допускается лямбда-выражениями:

  1. Лямбда-выражения будут использоваться только для реализации функциональных интерфейсов. Это первое серьезное ограничение.
  2. Хотя это можно сделать:

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

    Это не совсем то же самое, что и второй фрагмент кода выше (в более сложном примере можно было бы обеспечить базовую реализацию в Shape и переопределить его в Rectangle и Square, и это было бы невозможно с lambdas). Кроме того, было бы правильно утверждать, что два назначения выше используют другой исходный код.

  3. Нельзя просто "отличать" типы, как с классами:

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

Другими словами, лямбда-выражения хорошо подходят для функционального программирования, а не для хорошего объектно-ориентированного дизайна.

Ответ 2

Я согласен с @yelliver & @cricket_007, что вы сделали, это просто использование анонимного класса вместо подкласса.

Вы написали

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

Это то же самое, что и

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);

Таким образом, два примера, которые вы создали, не совпадают. В первом примере используется подкласс, а второй - анонимный класс.

Поэтому я думаю, что это может быть альтернативой полиморфизму

Очевидно, что это не альтернатива, это просто еще один способ реализации.

Преимущества lambda expression в Java.

1. Меньше строк кода

В приведенном выше примере вы можете видеть, что анонимный класс изменяется просто ->.

2. Последовательное и параллельное выполнение Поддержка путем прохождения поведения в методах

Вы можете перебирать любую коллекцию, просто используя foreach(). ( пример)

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

3. Лямбда-выражение и объекты

В Java любое лямбда-выражение является объектом как экземпляр функционального интерфейса. Мы можем присвоить лямбда-выражение любой переменной и передать ее как любой другой объект ( пример). лайк

list.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());

Ответ 3

лямбда-выражения в Java 8 представляют собой экземпляр функционального интерфейса (интерфейс только с одним методом). В вашем втором подходе вы просто создаете анонимный класс. Это не имеет смысла, если вы хотите повторно использовать код. Для получения дополнительной информации вы можете обратиться к вопросу о том, почему/когда использовать анонимный класс: как анонимные (внутренние) классы используются в 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);
}

Ответ 4

И императивный стиль, и функциональный стиль имеют свои плюсы и минусы. в этом конкретном случае использование подхода с функциональным стилем лучше просто потому, что вам не нужно создавать класс для каждого случая, когда вам нужно было инкапсулировать единую функциональность.

Поскольку можно быстро заметить создание класса для каждого случая, когда вам нужно было инкапсулировать один элемент функциональности, приводит к ненужному шаблону кода

С императивным подходом можно написать код и составить его, как вы идете. можно даже закончить писать классы по классам, не до конца понимая, что будет реализовано, что может привести к большой и неустойчивой кодовой базе.

С другой стороны, функциональный подход заставляет человека лучше понимать их реализацию до &, пока мы кодируем.

Тем не менее, очень важно понять, что само функциональное программирование не следует рассматривать как "замену" объектно-ориентированного программирования.

Фактически вы можете найти свое отношение к методу ООП, когда бизнес-логика становится более сложной.

Ответ 5

На мой взгляд, второй подход более гибкий, поскольку логика рисования отделена от самих фигур.

Итак, давайте сосредоточимся на втором подходе. @FunctionalInterface является необязательным, поскольку любой данный интерфейс считается функциональным интерфейсом, если он имеет только один абстрактный метод (также называемый интерфейсами SAM).

Как я уже сказал, второй подход более благоприятен, поскольку любой подход, способствующий разделению кода, приводит к многоразовому, поддерживаемому и слабо связанному решению.

Ваше второе решение более склонно к поведенческому подходу. Один из способов реализации этого подхода (особенно, если ваше поведение может быть представлено как маленькое одиночное выражение) является выражением лямбда.

Так, например, с учетом формы прямоугольника, нарисованной в зависимости от заданного контекста, вы можете создать два отдельных лямбда-выражения (поведения) и передать их в логику рисования при изменении контекста.

    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."));

Вы не можете попробовать фрагмент кода выше в первом примере, потому что логика рисования жестко закодирована в самих фигурах.

Подводя итог, всегда отдавайте предпочтение развязанным решениям, и если ваше поведение, которое нужно развязать, может быть представлено как маленькие одиночные выражения, лучший способ приблизиться к нему - использовать лямбда-выражения или если ваше поведение уже реализовано в другом месте, вы можете воспользоваться методом ссылки.