Как я полюбил лямбда выражения

Когда я начал знакомится с Java и столкнулся с Lambda Expressions мне идея их применения показалась избыточной, как собственно и анонимные классы. Давайте немного пока порассуждаем о последних. Итак, появление анонимных классов дало возможность программисту не заморачиваться на писании отдельного класса, тогда когда этот класс нужен с одной только целью — сделать то что нужно здесь и сейчас. Круто, однако элегантная на первый взгляд конструкция в моем представлении не казалась столь элегантной. Первое что меня лично раздражало визуальное раздувание кода. Вот вам пример:

package ru.bolotnikoff;

public abstract class MyAbstract {

    abstract void doMagic(int x);

}
________________________________________
package ru.bolotnikoff;

public class Main {

    static int y;

    public static void main(String[] args) {

        y = 2;

        MyAbstract myAbstract = new MyAbstract() {
            @Override
            void doMagic(int x) {
                y = y + x;
            }
        };

        myAbstract.doMagic(2);

        System.out.println(y);
        
    }
}

И это еще довольно примитивный пример. В реальности же определение анонимного класса передается прямо в метод другого класса, и тогда мы вообще имеем такую конструкцию:

System.out.println(myClass.anyOperation(new MyAbstract() {
    @Override
        int doMagic(int x) {
            return x + x;
        }
    }, 3));
}

На таких довольно простых примерах не совсем видно как загромождается код. Да и в случае чего, можно поиграться отступами, пробельчиками там… Второй вещью которая меня смущала еще больше был тот факт что мне приходилось смешивать логику разных объектов. Я попробую объяснить что я имею ввиду. Предположим у нас есть некоторый метод:

@FXML
public void initialize(){
    tableColoumnAuthor.setOnEditStart(
                new EventHandler<TableColumn.CellEditEvent<Book, String>>() {
         @Override
         public void handle(TableColumn.CellEditEvent<Book, String> event) {
             ...   
         }
  });
...

Как мне тогда казалось, весьма не красиво, не эстетично в методе отвечающем только за инициализацию сразу описывать логику другого объекта. Метод initialize() должен заполнить требуемые поля класса реальными объектами, подготовить все необходимые для работы параметры и весело сказать adios amigo! А подливало масло в огонь мое воображение, в котором я в том-же initialize() инициализирую 50 кнопок и вешаю на каждую из них обработчик нажатия в виде анонимного класса. Не знаю как у вас, а вот у меня от вида такой картины начали бы слезиться глаза.

Что же касается лямбда выражений — мне они представлялись еще менее нужной конструкцией т.к. работали только с интерфейсами особого вида — функциональными, что несколько ограничивает их. Да и читаемость их мне тоже казалась более сложной. Моим выбором было создание полноценного класса, реализующего требуемый интерфейс. Вся его логика только в нем, Нет лишнего кода, нет нагромождения и путаницы.

Так я думал ровно до момента когда не начал писать приложения сложнее калькулятора. Но вот я пишу более-менее достойное приложение, еще и потоки разные использую, и тут вообще начинается лапшеподобная возня со всеми этими слушателями, коллбеками… Посмотрим на примере Android приложения. Допустим у меня есть некий TextView на котором по нажатию на кнопку отображается допустим данные некоего термодатчика. Итак, мне нужно описать класс слушатель MyListener, и по хорошему интерфейс Recipient. Давайте посмотрим на код:

public class MyListener implements View.OnClickListener {
    Recipient r;
    MyListener(Recipient recipient){
        r = recipient;
    }
    @Override
    public void onClick(View v) {
        float sensorData;
        ...
        r.onDataReceive(sensorData);
    }
}
__________________________________________
public interface Recipient {
    <T> void onDataReceive(T data);
}
__________________________________________
// Естественно теперь наша Activity должна реализовывать Recipient
public class MainActivity extends AppCompatActivity implements Recipient{
    ...
    @Override
    public <T> void onDataReceive(T data) {
        // Тут мы обрабатываем и выводим полученные данные
    }
}

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

Button myButton = findViewById(R.id.button);
myButton.setOnClickListener((v)-> new Thread(()->{
    float thermalSensor2 = SensorsHolder.getData(2);
    runOnUiThread(()->thermalSensorView.setText(String.valueOf(thermalSensor2)));
}).start());

Ну и как вам разница? вопрос решен «не отходя от кассы». Во всей этой мешанине скобочек () и стрелочек -> конечно приходится «покопаться» дабы понять что тут происходит, но что мы получаем взамен? Взамен я получаю то что я пишу код, который работает «здесь и сейчас», а не блуждаю в бесконечном лабиринте абстракций. Отдельно следует сказать про необходимость таскать за собой передавать в конструктор MyListener получателя данных.

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

Поделиться этим материалом