Статьи

Как не использовать методы Java 8 по умолчанию

Предупреждение: вы не можете сделать это невидимым после того, как прочитали

Я говорил о множественном наследовании методов по умолчанию в последней статье блога и о том, как они ведут себя во время компиляции и выполнения. На этой неделе я расскажу, как использовать методы по умолчанию для реального наследования, для которого методы по умолчанию не были предназначены. По этой причине, пожалуйста, прочитайте эти строки на свой страх и риск, и не подразумевайте, что это шаблон, которому нужно следовать, также не подразумевайте обратное. То, что я пишу здесь, — это некоторые методы кодирования, которые могут быть созданы с использованием Java 8, но их удобство использования сомнительно, по крайней мере, для меня. Я также немного боюсь выпустить немного ифрита из бутылки, но, с другой стороны, эти ифриты просто не остаются там в любом случае. Когда-нибудь кто-нибудь выпустит это. По крайней мере, я прилагаю предупреждающий знак.

Пример проблемы

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

1
2
public String getName(){...}
public void setName(String name){...}

методы, которые были просто сеттерами и геттерами, запах запаха кода копилки просто наполнил комнату невыносимо. Поэтому мы создали класс

1
2
3
4
class HasName {
  public String getName(){...}
  public void setName(String name){...}
}

и каждый из классов, у которых было имя, просто расширял этот класс. На самом деле это не работало в течение длительного времени. Были классы, которые расширяли уже другие классы. В этом случае мы просто пытались переместить HasName вверх в строке наследования, но в некоторых случаях это просто не работало. Когда мы поднялись наверх, достигнув вершины, мы поняли, что у этих классов и некоторых других их потомков нет имени, зачем их заставлять? Если честно, в реальной жизни это было немного сложнее, чем просто иметь имя. Если бы это были только имена, мы могли бы жить с другими классами, имеющими имена. Это было что-то более сложное, что только сделало бы тему еще более сложной и поверьте мне: это будет достаточно сложно.

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

Интерфейс HasName с реализацией по умолчанию

Методы по умолчанию просто предоставляют функциональность по умолчанию. Метод по умолчанию может обращаться к this переменной, которая всегда является объектом, реализующим интерфейс и от имени которого вызывался метод. Если есть интерфейс I и класс C реализует интерфейс, то когда метод объекта C c вызывается переменной, this фактически объект c . Как бы вы реализовали getName() и setName() ?

Это методы установки и получения, которые обращаются к переменной String, которая находится в объекте. Вы не можете получить доступ к этому через интерфейс. Но не обязательно, чтобы значение хранилось в объекте. Единственным требованием является то, что все, что установлено для объекта, — это то же самое, что и get. Мы можем хранить значение где-то еще, по одному для каждого экземпляра объекта. Таким образом, нам нужно некоторое значение, которое можно связать с объектом, и время жизни значения должно быть таким же, как и время жизни объекта. Это звонит в звонок?

Это слабая хеш-карта! Да, это. И используя это, вы можете легко реализовать интерфейс HasName .

01
02
03
04
05
06
07
08
09
10
11
public interface HasName {
    class Extensions {
        private static final WeakHashMap<HasName, String> map = new WeakHashMap<>();
    }
    default void setName(String name) {
        Extensions.map.put(this, name);
    }
    default String getName() {
        return Extensions.map.get(this);
    }
}

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

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

Вывод

Интерфейсы не могут иметь полей экземпляра. Почему? Потому что в этом случае они были не интерфейсами, а классами. У Java нет множественного наследования реализации. Возможно, есть, но типа «пожалуйста, не используйте». Метод по умолчанию является технологической ошибкой. Вы можете назвать это компромиссом. Что-то, что было необходимо для сохранения обратной совместимости библиотек JDK при расширении функциональными методами. Тем не менее, вы можете имитировать поля в интерфейсах, используя слабые хеш-карты, чтобы получить доступ к унаследованному классу «vtable» полей и методов для делегирования. С этим вы можете сделать реальное множественное наследование. Тип, о котором твоя мама всегда тебя предупреждала. Я сказал тебе, приятель!

Еще одно предупреждение: приведенная выше реализация НЕ является поточно-ориентированной. Если вы попытаетесь использовать его в многопоточной среде, вы можете получить ConcurrentModificationException или даже произойти, если вызов get() для слабой хэш-карты попадет в бесконечный цикл и никогда не вернется. Я не говорю, как исправить использование слабых хэш-карт в этом сценарии. Или, ну, я передумал, и я делаю: использовать методы по умолчанию только для которых они были предназначены.

Ссылка: Как не использовать методы Java 8 по умолчанию от нашего партнера по JCG Питера Верхаса из блога Java Deep .