Статьи

Избавление от анонимных классов

Мне действительно нравится писать и читать лямбда-выражения — они лаконичны, выразительны и модны (да ладно, это не имеет большого значения!). Сравните это с анонимными классами, которые не являются ни одной из этих вещей. Именно поэтому мне нравится от них избавляться!

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

обзор

Чтобы все знали, о чем мы говорим, я начну с краткого обзора анонимных классов. Затем я объясню, почему я хотел бы избавиться от них, прежде чем определить их последнюю крепость и как ее победить.

Краткий обзор анонимных классов

Анонимные классы используются для создания специальной реализации интерфейса или абстрактного класса, например:

Пример для аномального класса

1
2
3
4
5
6
Runnable run = new Runnable() {
    @Override
    public void run() {
        runThisThing(someArgument);
    }
};

Это действительно создает отдельный класс (вы найдете его .class файл рядом с тем, который содержит этот код), но так как у него нет имени, вы догадались, он называется анонимным классом. Я всегда считал, что эти занятия должны быть очень короткими. Один, может быть, два метода с парой строк. Все более длинное и определенно все с состоянием, кажется, заслуживает своего имени и места — либо в нижней части файла как вложенный класс, либо даже как отдельный. Меня всегда смущает чтение метода, который в какой-то момент создает 10-строчную реализацию «кто знает, что», что делает что-то совершенно не связанное. Но для коротких реализаций (как в примере выше) анонимные классы были лучшим выбором.

Так что с ними не так?

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

Просто сравните это с примером выше:

Пример для аномального класса

1
Runnable run = () -> runThisThing(someArgument);

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

Избавление от анонимных классов

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

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

Затем, конечно, появилась Java 8, и благодаря лямбда-выражениям огромное количество вариантов использования анонимных классов просто исчезло. Это здорово! И это также инструмент, позволяющий избавиться от их последней цитадели: реализации «почти функциональных» интерфейсов и абстрактных классов с помощью одного или двух абстрактных методов.

Итак, вот моя идея:

Когда мы сталкиваемся с интерфейсом или абстрактным классом, который может быть реализован ad-hoc, мы создаем функциональную реализацию . Это неабстрактный класс, который делегирует все вызовы методов функциональным интерфейсам, которые были указаны при создании.

пример

Я думаю, пример прояснит это:

Почти функциональный интерфейс

1
2
3
4
5
6
7
public interface ValueListener<T> {
 
    void invalidated(T formerValue);
 
    void changed(T formerValue, T newValue);
 
}

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

Создание анонимной реализации

01
02
03
04
05
06
07
08
09
10
11
12
ValueListener<String> anonymousListener = new ValueListener<String>() {
 
    @Override
    public void invalidated(String formerValue) {
        valueInvalidated(formerValue);
    }
 
    @Override
    public void changed(String formerValue, String newValue) {
        valueChanged(formerValue, newValue);
    }
};

Вместо этого мы можем однажды создать функциональную реализацию интерфейса:

Функциональная реализация

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FunctionalValueListener<T> implements ValueListener<T> {
 
    private final Consumer<T> invalidated;
    private final BiConsumer<T, T> changed;
 
    public FunctionalValueListener(
            Consumer<T> invalidated,
            BiConsumer<T, T> changed) {
        this.invalidated = invalidated;
        this.changed = changed;
    }
 
    @Override
    public void invalidated(T formerValue) {
        invalidated.accept(formerValue);
    }
 
    @Override
    public void changed(T formerValue, T newValue) {
        changed.accept(formerValue, newValue);
    }
 
}

Экземпляры этого класса могут быть созданы гораздо более кратко и менее запутанно:

Реализация функциональной реализации

1
2
3
ValueListener<String> functionalListener = new FunctionalValueListener<>(
        this::valueInvalidated,
        this::valueChanged);

Другой пример

На самом деле эту идею вызвали многочисленные анонимные реализации AbstractAction Swing, которые я вижу в нашей базе кода:

1
2
3
4
5
6
Action action = new AbstractAction() {
    @Override
    public void actionPerformed(ActionEvent e) {
        performedAction(e);
    }
};

Это кричит «ЛАМБДА ЭКСПРЕССИЯ!» но вы не можете использовать его на абстрактных классах. Но после создания функциональной реализации, которая требует только Consumer<ActionEvent> вы можете, и это выглядит так:

1
Action action = new FunctionalAction(this::performedAction);

Намного лучше, верно?

Следовать за

Я попробую это в течение нескольких недель и сообщу, как это работает. Я уже вижу некоторые проблемы (ряд функциональных интерфейсов, предоставляемых JDK и исключения) и, по крайней мере, один способ улучшить этот шаблон.

Но я думаю, что стоит обсудить этот подход. Я тоже так думаю, почему бы тебе не поделиться этим?

Ты тоже попробуешь? Думал о новых проблемах или улучшении? Может быть, вы просто думаете, что это глупо? В любом случае оставьте комментарий, напишите пост или пингуйте меня везде, где вы меня найдете.

отражение

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

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

Ссылка: Избавление от анонимных классов от нашего партнера JCG Николая Парлога в блоге CodeFx .