Старый вопрос.Я удивлен, что никто не процитировал канонические источники: Java: обзор Джеймса Гослинга, Шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения Бандой четырех или Эффективная Java Джошуа Блоха (среди других источников).
Я начну с цитаты:
Интерфейс - это просто спецификация набора методов, которые объектотвечает на.Он не включает никаких переменных экземпляра или реализации.Интерфейсы могут быть множественным наследованием (в отличие от классов), и они могут использоваться более гибким способом, чем обычная жесткая структура наследования классов.(Гослинг, с.8)
Теперь давайте по очереди рассмотрим ваши предположения и вопросы (я добровольно проигнорирую возможности Java 8).
Предположения
Интерфейс - это коллекция ТОЛЬКО абстрактных методов и конечных полей.
Вы видели ключевое слово abstract
в интерфейсах Java?Нет. Тогда вы не должны рассматривать интерфейс как набор абстрактных методов.Возможно, вас вводят в заблуждение так называемые интерфейсы C ++, которые являются классами только с чисто виртуальными методами.C ++ по своему дизайну не имеет (и не должен иметь) интерфейсов, потому что он имеет множественное наследование.
Как объяснил Гослинг, вы должны скорее рассматривать интерфейс как "набор методов, которые объектотвечает на ".Мне нравится видеть интерфейс и соответствующую документацию в качестве сервисного контракта.Он описывает, что вы можете ожидать от объекта, который реализует этот интерфейс.В документации должны быть указаны предварительные и последующие условия (например, параметры должны быть не нулевыми, выходные данные всегда положительными, ...) и инварианты (метод, который не изменяет внутреннее состояние объекта).Этот контракт является сердцем, я думаю, ООП.
В Java нет множественного наследования.
Действительно.
JAVA опускает многие редко используемые, плохопонятные, запутанные возможности C ++, которые по нашему опыту приносят больше горя, чем пользы.Это в первую очередь состоит из перегрузки операторов (хотя она и имеет перегрузку методов), множественного наследования и обширных автоматических приведений.(Гослинг, с.2)
Ничего добавить.
Интерфейсы могут использоваться для достижения множественного наследования в Java.
Нет, simlpy, потому что нетмножественное наследование в Java.См. Выше.
Одна сильная сторона наследования заключается в том, что мы можем использовать код базового класса в производном классе, не записывая его снова.Может быть, это самое важное для наследования.
Это называется "наследование реализации".Как вы писали, это удобный способ многократного использования кода.
Но у него есть важный аналог:
родительские классы часто определяют хотя бы часть физического представления своих подклассов.Поскольку наследование предоставляет подклассу детали реализации его родителя, часто говорят, что «наследование нарушает инкапсуляцию» [Sny86].Реализация подкласса становится настолько связанной с реализацией его родительского класса, что любое изменение в реализации родителя заставит подкласс измениться.(GOF, 1.6)
(в Блохе есть аналогичная цитата, пункт 16).
На самом деле наследование служит и другой цели:
Наследование классов сочетает в себе наследование интерфейса и наследование реализации.Наследование интерфейса определяет новый интерфейс в терминах одного или нескольких существующих интерфейсов.Наследование реализации определяет новую реализацию в терминах одной или нескольких существующих реализаций.(GOF, Приложение A)
Оба используют ключевое слово extends
в Java.У вас могут быть иерархии классов и иерархии интерфейсов.Первые делятся реализацией, вторые делят обязательства.
Вопросы
Q1. Поскольку интерфейсы имеют только абстрактные методы (без кода), как мы можем сказать, что если мы реализуем какой-либо интерфейс, то это наследование? Мы не используем его код. **
Реализация интерфейса не наследование. Это реализация. Таким образом, ключевое слово implements
.
Q2. Если реализация интерфейса не является наследованием, то как интерфейсы используются для достижения множественного наследования? **
Нет множественного наследования в Java. Смотри выше.
Q3. В любом случае, какова польза от использования интерфейсов? У них нет никакого кода. Нам нужно снова и снова писать код во всех классах, которые мы его реализуем. / Тогда зачем создавать интерфейсы? / Каковы точные преимущества использования интерфейсов? Действительно ли мы получаем множественное наследование с помощью интерфейсов?
Самый важный вопрос: почему вы хотите иметь множественное наследование? Я могу придумать два ответа: 1. дать типы множественных объектов объекту; 2. повторно использовать код.
Присвоение объектам типов Mutliple
В ООП один объект может иметь разных типов . Например, в Java ArrayList<E>
имеет следующие типы: Serializable
, Cloneable
, Iterable<E>
, Collection<E>
, List<E>
, RandomAccess
, AbstractList<E>
, AbstractCollection<E>
и Object
( Надеюсь я никого не забыл). Если объект имеет разные типы, различные потребители смогут использовать его, не зная о его особенностях. Мне нужен Iterable<E>
, а вы даете мне ArrayList<E>
? Все нормально. Но если мне нужен List<E>
, а вы даете мне ArrayList<E>
, это тоже нормально. И т.д.
Как вы вводите объект в ООП? В качестве примера вы взяли интерфейс Runnable
, и этот пример идеально подходит для иллюстрации ответа на этот вопрос. Я цитирую официальный документ Java:
Кроме того, Runnable предоставляет средства для того, чтобы класс был активным, не наследуя Thread.
Вот в чем смысл: Наследование - это удобный способ ввода объектов. Вы хотите создать поток? Давайте создадим подкласс класса Thread
. Вы хотите, чтобы объект имел разные типы, давайте используем mutliple-наследование. Argh. Это не существует в Java. (В C ++, если вы хотите, чтобы объект имел разные типы, множественное наследование - это путь.)
Как тогда придавать объектам типы mutliple? В Java вы можете напечатать свой объект напрямую . Это то, что вы делаете, когда ваш класс implements
интерфейс Runnable
. Зачем использовать Runnable
, если вы поклонник наследства? Возможно, потому что ваш класс уже является подклассом другого класса, скажем, A
. Теперь у вашего класса есть два типа: A
и Runnable
.
При наличии нескольких интерфейсов вы можете присвоить объекту несколько типов. Вам просто нужно создать класс, который implements
несколько интерфейсов. Пока вы соблюдаете условия контрактов, все в порядке.
Повторное использование кода
Это сложный предмет; Я уже процитировал GOF о нарушении инкапсуляции. Другой ответ затронул проблему алмазов. Можно также подумать о принципе единой ответственности:
У класса должна быть только одна причина для изменения. (Роберт К. Мартин, Agile Software Development, принципы, шаблоны и практики)
Наличие родительского класса может дать классу причину для изменения, помимо его собственных обязанностей:
Реализация суперкласса может меняться от релиза к релизу, и, если это так, подкласс может сломаться, даже если его код не был затронут. Как следствие, подкласс должен развиваться в тандеме со своим суперклассом (Блох, пункт 16).
Я бы добавил более прозаическую проблему: у меня всегда возникает странное чувство, когда я пытаюсь найти исходный код метода в классе, но не могу его найти. Тогда я помню: это должно быть определено где-то в родительском классе. Или в классе бабушек и дедушек. Или, может быть, даже выше. Хорошая IDE в этом случае является ценным активом, но, на мой взгляд, остается чем-то волшебным. Ничего похожего с иерархиями интерфейсов, так как мне нужен только javadoc: один ярлык клавиатуры в IDE, и я его получаю.
У наследства есть преимущества:
Безопасно использовать наследование в пакете, где реализации подкласса и суперкласса находятся под контролем одних и тех же программистов. Также безопасно использовать наследование при расширении классов, специально разработанных и задокументированных для расширения (пункт 17: Разработка и документация для наследования или иное запрещение). (Блох, п.16)
Примером класса, "специально разработанного и задокументированного для расширения" в Java, является AbstractList
.
Но Блох и GOF настаивают на этом: «Пользуйся композицией по наследству»:
Делегирование - это способ сделать композицию такой же мощной для повторного использования, как наследование [Lie86, JZ91]. При делегировании два объекта участвуют в обработке запроса: принимающий объект делегирует операции своему делегату. Это аналогично подклассам, откладывающим запросы к родительским классам. (GOF стр.32)
Если вы используете композицию, вам не придется писать один и тот же код снова и снова. Вы просто создаете класс, который обрабатывает дубликаты, и передаете экземпляр этого класса классам, которые реализует интерфейс. Это очень простой способ повторного использования кода. И это поможет вам следовать принципу единой ответственности и сделать код более тестируемым. У Rust и Go нет наследования (у них тоже нет классов), но я не думаю, что код более избыточен, чем в других языках ООП.
Кроме того, если вы используете композицию, вы естественным образом будете использовать интерфейсы, чтобы придать вашему коду необходимую структуру и гибкость (см. Другие ответы о вариантах использования интерфейсов).
Примечание: вы можете поделиться кодом с интерфейсами Java 8
И, наконец, последняя цитата:
Во время незабываемой сессии вопросов и ответов кто-то спросил его [Джеймса Гослинга]: «Если бы вы могли снова заняться Java, что бы вы изменили?» «Я бы пропустил занятия» (где-нибудь в сети, не знаю, правда ли это)