В Java 8 интерфейсы могут содержать реализованные методы, статические методы и так называемые методы «по умолчанию» (которые классам реализации не нужно переопределять).
На мой (возможно, наивный) взгляд, не было необходимости нарушать подобные интерфейсы. Интерфейсы всегда были контрактом, который вы должны выполнить, и это очень простая и чистая концепция. Теперь это смесь нескольких вещей. По моему мнению:
- статические методы не принадлежат интерфейсам. Они относятся к служебным классам.
- методы по умолчанию не должны были быть разрешены в интерфейсах вообще. Вы всегда можете использовать абстрактный класс для этой цели.
Короче говоря:
До Java 8:
- Вы можете использовать абстрактные и обычные классы для предоставления статических и стандартных методов. Роль интерфейсов понятна.
- Все методы в интерфейсе должны быть переопределены путем реализации классов.
- Вы не можете добавить новый метод в интерфейс без изменения всех реализаций, но на самом деле это хорошая вещь.
После Java 8:
- Практически нет разницы между интерфейсом и абстрактным классом (кроме множественного наследования). На самом деле вы можете эмулировать обычный класс с интерфейсом.
- При программировании реализаций программисты могут забыть переопределить методы по умолчанию.
- Существует ошибка компиляции, если класс пытается реализовать два или более интерфейсов, имеющих метод по умолчанию с одной и той же сигнатурой.
- Добавляя метод по умолчанию к интерфейсу, каждый реализующий класс автоматически наследует это поведение. Некоторые из этих классов, возможно, не были разработаны с учетом этой новой функциональности, и это может вызвать проблемы. Например, если кто-то добавляет новый метод
default void foo()
по умолчанию к интерфейсуIx
, класс,Cx
реализующийIx
и имеющий закрытыйfoo
метод с той же сигнатурой, не компилируется.
Каковы основные причины таких серьезных изменений и какие новые преимущества (если таковые имеются) они добавляют?
java
programming-languages
interfaces
java8
Мистер смит
источник
источник
@Deprecated
категории! статические методы - одна из самых злоупотребляемых конструкций в Java из-за невежества и лени. Множество статических методов обычно означает некомпетентный программист, увеличивающий связь на несколько порядков и являющийся кошмаром для юнит-тестирования и рефакторинга, когда вы понимаете, почему они плохая идея!Ответы:
Хороший мотивирующий пример для методов по умолчанию находится в стандартной библиотеке Java, где у вас теперь есть
вместо
Я не думаю, что они могли бы сделать это иначе без более чем одной идентичной реализации
List.sort
.источник
IEnumerable<Byte>.Append
их, чтобы присоединиться к ним, а затем вызовитеCount
и расскажите, как методы расширения решают проблему. Если быCountIsKnown
иCount
были членамиIEnumerable<T>
, возвращениеAppend
могло бы рекламировать,CountIsKnown
если составляющие коллекции сделали, но без таких методов это невозможно.Правильный ответ на самом деле находится в документации Java , которая гласит:
Это было давним источником боли в Java, потому что интерфейсы, как правило, невозможно развивать после того, как они были обнародованы. (Содержание в документации относится к статье, на которую вы ссылались в комментарии: Эволюция интерфейса с помощью методов виртуального расширения .) Кроме того, быстрое внедрение новых функций (например, лямбда-выражений и новых потоковых API) может быть сделано только путем расширения существующие интерфейсы коллекций и предоставление реализаций по умолчанию. Нарушение бинарной совместимости или внедрение новых API означало бы, что пройдет несколько лет, прежде чем наиболее важные функции Java 8 станут широко использоваться.
Причина, по которой статические методы допускаются в интерфейсах, снова раскрывается в документации: [t] он упрощает организацию вспомогательных методов в ваших библиотеках; вы можете хранить статические методы, специфичные для интерфейса, в том же интерфейсе, а не в отдельном классе. Другими словами, статические служебные классы, такие как,
java.util.Collections
теперь (в конечном итоге) можно считать анти-паттерном, вообще (конечно, не всегда ). Я предполагаю, что добавление поддержки этого поведения было тривиальным, как только были реализованы методы виртуального расширения, иначе это, вероятно, не было бы сделано.На аналогичном замечании пример того, как эти новые функции могут быть полезны, - рассмотреть один класс, который недавно меня раздражал
java.util.UUID
. На самом деле он не обеспечивает поддержку типов UUID 1, 2 или 5, и его нельзя легко изменить для этого. Он также застрял с предопределенным генератором случайных чисел, который нельзя переопределить. Реализация кода для неподдерживаемых типов UUID требует либо прямой зависимости от стороннего API, а не интерфейса, либо поддержки кода преобразования и затрат на дополнительную сборку мусора. С помощью статических методовUUID
можно было бы вместо этого определить интерфейс, что позволило бы реализовать сторонние реализации отсутствующих элементов. (Если быUUID
изначально он был определен как интерфейс, у нас, вероятно, был бы какой-то неуклюжийUuidUtil
класс со статическими методами, что тоже было бы ужасно.) Многие из основных API-интерфейсов Java деградируют из-за того, что не основываются на интерфейсах, но в Java 8 количество оправданий для такого плохого поведения, к счастью, уменьшилось.Неправильно говорить, что здесь [t] практически нет разницы между интерфейсом и абстрактным классом , потому что абстрактные классы могут иметь состояние (то есть объявлять поля), а интерфейсы - нет. Поэтому он не эквивалентен множественному наследованию или даже наследованию в смешанном стиле. Правильные миксины (такие как черты Groovy 2.3 ) имеют доступ к состоянию. (Groovy также поддерживает статические методы расширения.)
По моему мнению, не очень хорошая идея следовать примеру Довала . Интерфейс должен определять контракт, но не должен обеспечивать выполнение контракта. (В любом случае, не в Java.) Правильная проверка реализации является обязанностью набора тестов или другого инструмента. Определение контрактов может быть сделано с помощью аннотаций, и OVal является хорошим примером, но я не знаю, поддерживает ли он ограничения, определенные на интерфейсах. Такая система возможна, даже если она не существует в настоящее время. (Стратегии включают настройку во время компиляции
javac
через процессор аннотацийAPI и генерация байт-кода во время выполнения.) В идеале, контракты должны выполняться во время компиляции, а в худшем случае - с использованием набора тестов, но я понимаю, что принудительное выполнение во время выполнения не одобряется. Еще одним интересным инструментом, который может помочь при программировании контрактов в Java, является Checker Framework .источник
default
методы не могут переопределятьequals
,hashCode
иtoString
. Очень информативный анализ затрат и выгод того, почему это запрещено, можно найти здесь: mail.openjdk.java.net/pipermail/lambda-dev/2013-March/…equals
и одинhashCode
метод, поскольку существует два разных типа равенства, которые могут понадобиться для тестирования коллекций, и элементы, которые будут реализовывать несколько интерфейсов, могут застрять с противоречивыми договорными требованиями. Возможность использовать списки, которые не будут меняться в качествеhashMap
ключей, полезна, но бывают также случаи, когда было бы полезно хранить коллекции, вhashMap
которых совпадают вещи, основанные на эквивалентности, а не на текущем состоянии [эквивалентность подразумевает соответствие состояния и неизменности ] ,Потому что вы можете наследовать только один класс. Если у вас есть два интерфейса, реализации которых достаточно сложны, и вам нужен абстрактный базовый класс, эти два интерфейса на практике взаимоисключающие.
Альтернативой является преобразование этих абстрактных базовых классов в коллекцию статических методов и превращение всех полей в аргументы. Это позволило бы любому разработчику интерфейса вызывать статические методы и получать функциональность, но это очень много стандартного языка, который уже слишком многословен.
В качестве мотивирующего примера того, почему может быть полезна возможность предоставления реализаций в интерфейсах, рассмотрим этот интерфейс стека:
Нет никакого способа гарантировать, что, когда кто-то реализует интерфейс,
pop
сгенерирует исключение, если стек пуст. Мы могли бы применить это правило, разделив егоpop
на два метода:public final
метод, обеспечивающий выполнение контракта, иprotected abstract
метод, который выполняет фактическое выталкивание.Мы не только гарантируем, что все реализации соблюдают контракт, мы также избавили их от необходимости проверять, пуст ли стек и генерировать исключение. Это большая победа! ... за исключением того факта, что нам пришлось изменить интерфейс на абстрактный класс. В языке с одним наследованием это большая потеря гибкости. Это делает ваши потенциальные интерфейсы взаимоисключающими. Способность обеспечить реализации, которые полагаются только на методы интерфейса, решит проблему.
Я не уверен, позволяет ли подход Java 8 к добавлению методов к интерфейсам добавлять конечные методы или защищенные абстрактные методы, но я знаю, что язык D позволяет это и обеспечивает встроенную поддержку для Design by Contract . В этом методе нет опасности, поскольку он
pop
является окончательным, поэтому ни один реализующий класс не может его переопределить.Что касается реализаций по умолчанию переопределяемых методов, я предполагаю, что любые реализации по умолчанию, добавленные в API Java, полагаются только на контракт интерфейса, к которому они были добавлены, поэтому любой класс, который правильно реализует интерфейс, также будет правильно работать с реализациями по умолчанию.
Кроме того,
Это не совсем так, поскольку вы не можете объявить поля в интерфейсе. Любой метод, который вы пишете в интерфейсе, не может полагаться на какие-либо детали реализации.
В качестве примера в пользу статических методов в интерфейсах рассмотрим вспомогательные классы, такие как Collections в Java API. Этот класс существует только потому, что эти статические методы не могут быть объявлены в их соответствующих интерфейсах.
Collections.unmodifiableList
с таким же успехом мог быть объявлен вList
интерфейсе, и его было бы легче найти.источник
Stack
интерфейс, и вы хотите, чтобы приpop
вызове с пустым стеком создавалось исключение. Учитывая абстрактные методыboolean isEmpty()
иprotected T pop_impl()
, вы могли бы реализоватьfinal T pop() { isEmpty()) throw PopException(); else return pop_impl(); }
Это обеспечивает соблюдение контракта на ВСЕХ разработчиков.static
.Возможно, целью было предоставить возможность создавать смешанные классы, заменив необходимость введения статической информации или функциональности через зависимость.
Эта идея, кажется, связана с тем, как вы можете использовать методы расширения в C # для добавления реализованной функциональности к интерфейсам.
источник
list.sort(ordering);
формы.IEnumerable
интерфейс в C #, вы увидите, как реализация методов расширения для этого интерфейса (как этоLINQ to Objects
делает) добавляет функциональность для каждого реализуемого классаIEnumerable
. Вот что я имел в виду под добавлением функциональности.Две основные цели, которые я вижу в
default
методах (некоторые варианты использования служат обеим целям):Если бы речь шла только о второй цели, вы бы не увидели это в совершенно новом интерфейсе
Predicate
. Все@FunctionalInterface
аннотированные интерфейсы должны иметь только один абстрактный метод, чтобы лямбда-код мог его реализовать. Добавленыdefault
методы , такие какand
,or
,negate
только полезность, и вы не должны переопределить их. Однако иногда статические методы будут лучше .Что касается расширения существующих интерфейсов - даже там, некоторые новые методы - просто синтаксический сахар. Методы ,
Collection
какstream
,forEach
,removeIf
- в основном, это просто утилита вам не нужно переопределить. И тогда есть методы, какspliterator
. Реализация по умолчанию является неоптимальной, но, по крайней мере, код компилируется. Прибегайте к этому, только если ваш интерфейс уже опубликован и широко используется.Что касается
static
методов, я полагаю, что другие достаточно хорошо это понимают: он позволяет интерфейсу быть своим собственным служебным классом. Может быть, мы могли бы избавитьсяCollections
в будущем Java?Set.empty()
будет качатьсяисточник