Предупреждение: вы не можете сделать это невидимым после того, как прочитали
Я говорил о множественном наследовании методов по умолчанию в последней статье блога и о том, как они ведут себя во время компиляции и выполнения. На этой неделе я расскажу, как использовать методы по умолчанию для реального наследования, для которого методы по умолчанию не были предназначены. По этой причине, пожалуйста, прочитайте эти строки на свой страх и риск, и не подразумевайте, что это шаблон, которому нужно следовать, также не подразумевайте обратное. То, что я пишу здесь, — это некоторые методы кодирования, которые могут быть созданы с использованием 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 . |