Мне действительно нравится писать и читать лямбда-выражения — они лаконичны, выразительны и модны (да ладно, это не имеет большого значения!). Сравните это с анонимными классами, которые не являются ни одной из этих вещей. Именно поэтому мне нравится от них избавляться!
В последние месяцы это осознание постепенно материализовалось, и вчера мое подсознание выдвинуло идею, как этого добиться. Я представлю это здесь, и через пару недель после этого опубликую пост.
обзор
Чтобы все знали, о чем мы говорим, я начну с краткого обзора анонимных классов. Затем я объясню, почему я хотел бы избавиться от них, прежде чем определить их последнюю крепость и как ее победить.
Краткий обзор анонимных классов
Анонимные классы используются для создания специальной реализации интерфейса или абстрактного класса, например:
Пример для аномального класса
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 . |