Статьи

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

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

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

обзор

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

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

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


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

Runnable run = new Runnable() {
@Override
public void run() {
runThisThing(someArgument);
}
};

Это действительно создает отдельный класс (вы найдете его .class файл рядом с тем, который содержит этот код), но так как у него нет имени, вы догадались, он называется анонимным классом.

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

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

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

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

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


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

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

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

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

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

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

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

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

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

пример

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


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

public interface ValueListener<T> {

void invalidated(T formerValue);

void changed(T formerValue, T newValue);

}

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


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

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

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


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

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

}

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


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

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

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

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

Action action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
performedAction(e);
}
};

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

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

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

Следовать за

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

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

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

отражение

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

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