Как имплементировать два интерфейса java
Имплементация от нескольких интерфейсов
Здравствуйте, подскажите пожалуйста, почему так нельзя реализовывать интерфейсы?
Multicast с нескольких интерфейсов
Нужно ловить мультикаст в IPv4 со всех физических интерфейсов (Windows и Linux). Для этого после.
Реализация нескольких интерфейсов в одном классе
добрый вечер, пожалуйста, помогите разобраться. есть средство передвижения, которое может вести.
Открытие нескольких интерфейсов в одном окне
Доброго времени суток! Есть главное окно, в нем пункты меню и стартовая информация. Есть код.
Являются ли хорошей практикой одинаковые имена методов нескольких интерфейсов в одной реализации?
Здравствуйте, да, в C# можно в одном классе реализовать несколько методов интерфейсов с одинаковыми.
Решение
В статье так и написано. Я лишь дополнил ответы отписавшихся
Имплементация Number
Хочу заимплементировать свои числа в С++. Подскажите наводящие темы и статьи. Смысл в динамическом.
Имплементация интерфейса
Привет есть такой вот код. один из методов должен складывать два числа, но вероятно надо делать.
Имплементация дженерик интерфейса
Добрый день. Не могу до конца осознать следующий код, может кто на пальцах объяснить? Есть.
Имплементация прогресс-бара
Привет! Вопрос возможно простой) Но я не знаю) Вот есть у меня копирование в файл-менеджере. Но.
Правильная имплементация объектов
Здравствуйте. Подскажите пожалуйста, сейчас слушаю лекции по программирования для iOS и там лектор.
Интерфейсы в Java
1. Знакомство с интерфейсами
Сегодня у вас прямо день знаний. Еще одна новая и интересная тема — это интерфейсы.
Вот несколько полезных фактов об интерфейсах:
1. Объявление интерфейса
Интерфейс может наследоваться только от интерфейсов. Зато родителей у интерфейса может быть много. Еще говорят, что в Java есть множественное наследование интерфейсов. Примеры:
3. Наследование классов от интерфейсов
Технически между словами extends и implements нет никакой разницы: и то, и то — это наследование. Так было сделано, чтобы повысить читабельность кода. В английском языке принято говорить, что классы наследуются ( extends ), а интерфейсы реализуются ( implements )
4. Переменные
И самое важное: в интерфейсах нельзя объявлять переменные (хотя статические можно).
А зачем же нужны интерфейсы? Когда их используют? У интерфейсов есть два сильных преимущества по сравнению с классами:
2. Отделение «описания методов» от их реализации.
С помощью интерфейсов это деление можно усилить еще больше. Мы сделаем специальный «класс для всех», и второй «класс для своих», который унаследуем от первого. Вот как это примерно будет:
Таким образом, мы скрываем не только различные реализации, но и сам класс, который ее содержит (везде в коде может фигурировать только интерфейс). Это позволяет очень гибко, прямо в процессе исполнения программы, подменять одни объекты на другие, меняя поведение объекта скрытно от всех классов, которые его используют.
Это очень мощная технология в сочетании с полиморфизмом. Сейчас далеко не очевидно, зачем так нужно делать. Вы сначала должны столкнуться с программами, состоящими из десятков или сотен классов, чтобы понять, что интерфейсы способны существенно упростить жизнь.
3. Множественное наследование
В Java все классы могут иметь только один класс-родитель. В других языках программирования классы часто могут иметь несколько классов-родителей. Это очень удобно, но приносит также много проблем.
В Java пришли к компромиссу: запретили множественное наследование классов, но разрешили множественное наследование интерфейсов. Интерфейс может иметь несколько интерфейсов-родителей. Класс может иметь несколько интерфейсов-родителей и только один класс-родитель.
Почему же множественное наследование классов запретили, а интерфейсов — разрешили? Все дело в так называемом пирамидальном наследовании:
Когда класс B наследуется от класса A, он ничего не знает о классах C и D. Поэтому он использует переменные класса A так, как считает нужным. Класс C делает то же самое: использует переменные класса A, но уже другим способом. И в классе D это все выливается в конфликт.
Давайте рассмотрим такой простой пример. Допустим, у нас есть 3 класса:
А т.к. интерфейсам запрещено иметь переменные, то и конфликта такого рода у них быть не может. Поэтому разрешено множественное наследование интерфейсов.
Интерфейсы в Java и немного о полиморфизме
Интерфейс – это контракт, в рамках которого части программы, зачастую написанные разными людьми, взаимодействуют между собой и со внешними приложениями. Интерфейсы работают со слоями сервисов, безопасности, DAO и т.д. Это позволяет создавать модульные конструкции, в которых для изменения одного элемента не нужно трогать остальные.
Новички часто спрашивают, чем интерфейс отличается от абстрактного класса. Интерфейсы в Java компенсируют отсутствие множественного наследования классов. У класса-потомка может быть только один абстрактный класс-родитель, а вот интерфейсов класс может применять (имплементировать) сколько угодно.
Интерфейс на Java объявляют примерно так же, как и класс:
В имплементирующем интерфейс классе должны быть реализованы все предусмотренные интерфейсом методы, за исключением методов по умолчанию.
Методы по умолчанию впервые появились в Java 8. Их обозначают модификатором default. В нашем примере это метод say_goodbye, реализация которого прописана прямо в интерфейсе. Дефолтные методы изначально готовы к использованию, но при необходимости их можно переопределять в применяющих интерфейс классах.
Функциональный интерфейс Java
Если у интерфейса только один абстрактный метод, перед нами функциональный интерфейс. Его принято помечать аннотацией @FunctionalInterface, которая указывает компилятору, что при обнаружении второго абстрактного метода в этом интерфейсе нужно сообщить об ошибке. Стандартных (default) методов у интерфейса может быть множество – в том числе принадлежащих классу java.lang.Object.
Как выглядит функциональный интерфейс на Java:
Функциональные интерфейсы появились в Java 8. Они обеспечили поддержку лямбда-выражений, использование которых делает код лаконичным и понятным:
В той же версии появились пакеты встроенных интерфейсов: java.util.function и java.util.stream.
Реализация интерфейсов классами Java
Допустим, есть интерфейс Edible, которым пользуются классы Fruit, Vegetable, Fish. Экземпляры этих классов можно создавать так:
Обратите внимание на разницу в конструкторах: для фруктов задаём название и сорт, для рыбы – название, район вылова и вес порции в граммах. Но ссылки на оба объекта храним в переменных одного типа – «Съестное».
Интерфейсы и полиморфизм
Пример выше иллюстрирует один из трех основополагающих принципов ООП — полиморфизм. Мы раскрыли одно и то же явление — съедобность — через несколько классов, свойства и методы которых частично отличаются. Представление разных форм одного явления — это и есть полиморфизм. Если нужно, такую систему всегда можно расширить и скорректировать. В нашем случае — добавить новые виды съестного и методы их приготовления.
В Java полиморфизм можно реализовать через:
Интерфейс выручает в ситуации, когда при создании переменной мы не знаем, объект какого класса ей будет присвоен.
Интерфейс – это контракт, в рамках которого части программы, зачастую написанные разными людьми, взаимодействуют между собой и со внешними приложениями. Интерфейсы работают со слоями сервисов, безопасности, DAO и т.д. Это позволяет создавать модульные конструкции, в которых для изменения одного элемента не нужно трогать остальные.
Новички часто спрашивают, чем интерфейс отличается от абстрактного класса. Интерфейсы в Java компенсируют отсутствие множественного наследования классов. У класса-потомка может быть только один абстрактный класс-родитель, а вот интерфейсов класс может применять (имплементировать) сколько угодно.
Интерфейс на Java объявляют примерно так же, как и класс:
В имплементирующем интерфейс классе должны быть реализованы все предусмотренные интерфейсом методы, за исключением методов по умолчанию.
Методы по умолчанию впервые появились в Java 8. Их обозначают модификатором default. В нашем примере это метод say_goodbye, реализация которого прописана прямо в интерфейсе. Дефолтные методы изначально готовы к использованию, но при необходимости их можно переопределять в применяющих интерфейс классах.
Функциональный интерфейс Java
Если у интерфейса только один абстрактный метод, перед нами функциональный интерфейс. Его принято помечать аннотацией @FunctionalInterface, которая указывает компилятору, что при обнаружении второго абстрактного метода в этом интерфейсе нужно сообщить об ошибке. Стандартных (default) методов у интерфейса может быть множество – в том числе принадлежащих классу java.lang.Object.
Как выглядит функциональный интерфейс на Java:
Функциональные интерфейсы появились в Java 8. Они обеспечили поддержку лямбда-выражений, использование которых делает код лаконичным и понятным:
В той же версии появились пакеты встроенных интерфейсов: java.util.function и java.util.stream.
Реализация интерфейсов классами Java
Допустим, есть интерфейс Edible, которым пользуются классы Fruit, Vegetable, Fish. Экземпляры этих классов можно создавать так:
Обратите внимание на разницу в конструкторах: для фруктов задаём название и сорт, для рыбы – название, район вылова и вес порции в граммах. Но ссылки на оба объекта храним в переменных одного типа – «Съестное».
Интерфейсы и полиморфизм
Пример выше иллюстрирует один из трех основополагающих принципов ООП — полиморфизм. Мы раскрыли одно и то же явление — съедобность — через несколько классов, свойства и методы которых частично отличаются. Представление разных форм одного явления — это и есть полиморфизм. Если нужно, такую систему всегда можно расширить и скорректировать. В нашем случае — добавить новые виды съестного и методы их приготовления.
В Java полиморфизм можно реализовать через:
Интерфейс выручает в ситуации, когда при создании переменной мы не знаем, объект какого класса ей будет присвоен.
Как имплементировать два интерфейса java
Что вы видите, когда садитесь за руль?
Скорее всего, по крайней мере так было в 2018 году, вы увидите руль, педали, рычаг коробки переключения передач и т.д. Другими словами, вы видите интерфейс взаимодействия с машиной.
Графическая составляющая программ не даром называется интерфейсом. Она определяет то, как вы будете взаимодействовать с программой, то как вы будете использовать функционал программы. То, как ведет себя программа. При этом вы можете абсолютно не знать деталей реализации интерфейса.
В случае с автомобилем вы можете абсолютно не иметь представления о том, какой мотор у вас используется, карбюратор у вас или инжектор, на каком языке написана программа, которой вы пользуетесь: вам, как обычному водителю, это не важно.
Интерфейс позволяет вам не знать деталей реализации, а просто взаимодействовать с объектом.
Точно тот же принцип используется и в программировании!
Если перенести эту мысль в область программирования, то можно сказать, что интерфейс определяет то, как мы можем использовать объект.
Список — это абстрактный тип данных, представляющий собой упорядоченный набор значений, в котором некоторое значение может встречаться более одного раза.
Подробнее про списки можно прочесть в википедии
Благодаря этому можно писать более гибкий и общий код. Например, вы хотите распечатать список строк. Вы можете написать код как построенный на использовании интерфейса, так и на использовании конкретной реализации.
Давайте опять проведем параллель с реальным миром и представим себе автомойку.
Этот совет касается не только передаваемых в метод ссылок, но и объявления свойств в классе, переменных в методе и т.д.
В начале своего пути начинающие программисты часто пишут код вида:
Т.е объявляя ссылку на объект используют конкретную реализацию.
Объявляя переменную помните, что вы объявляете поведение, которое ждете от объекта. Точно также, как и в случае, когда вы передаете объект.
Поэтому везде, где это возможно, старайтесь пользоваться интерфейсами, передавать и возвращать их из методов, объявлять параметром класса и т.д.
Какие методы могут быть объявлены в интерфейсе:
Что можно сказать об абстрактных методах в интерфейсе:
Класс не обязательно должен определять все абстрактные методы реализуемого интерфейса, но в таком случае он сам должен быть абстрактным классом.
Здесь тоже все логично: мы реализовываем интерфейс, тем самым добавляем себе новый метод, так как этот метод абстрактный, то мы либо должны его определить, либо быть абстрактным классом.
Начиная с Java 8 стало возможно описание статического метода в интерфейсе. Тут все также как у обычного статического метода:
Статические методы у интерфейса очень удобны для группирования utility или factory методов.
Если вы не знаете что такое factory метод, то советую посмотреть:
При этом, чтобы не было путаницы, в Java статический метод, определенный в интерфейсе можно вызвать только явно через интерфейс его содержащий.
Метод с реализацией по умолчанию
В Java 8 добавили возможность реализации метода по умолчанию, так называемой default реализации.
Реализация по умолчанию удобна, когда большинство классов, реализующих интерфейс, будут определять метод, содержащийся в этом интерфейсе, одинаково. И в таком случае, удобно, когда вы не дублируете один и тот же кусок реализации в каждый класс, а сделали реализацию по умолчанию.
В таком случае все классы, где реализация одинакова, будут использовать то, что объявлено по умолчанию, а те классы, которым такая реализация не подходит, просто переопределят ее.
Использование методов с реализацией по умолчанию позволит избежать дублирования кода, при этом не теряется ни гибкость, ни читабельность.
Если коротко, то такие методы введены для внутреннего использования, чтобы убрать повторяемость кода, которая может возникнуть в некоторых случаях.
Такие методы могут быть статическими и нестатическими, но они не могут иметь реализации по умолчанию. Такие методы обязаны быть реализованы сразу и могут использоваться только внутри самого интерфейса, в котором они определены.
Сделано это для упрощения написания кода, когда вам в интерфейсе необходимо выполнить повторяющиеся действия:
Интерфейс может быть пустым, т.е не содержать никаких объявлений:
Как следует из названия, задача интерфейса-маркера сообщить о наличии определённого поведения у объектов класса, помеченного таким интерфейсом.
О клонировании объектов можно прочесть тут
В интерфейсах мы можем описывать не только методы, но и свойства.
Помимо методов в интерфейсах могут быть определены еще и константы.
Эту возможность разработчики, особенно раньше, часто используют для хранения констант в интерфейсах и использование их в классах, реализующих такие интерфесы.
Этот подход имеет право на жизнь, до сих пор часто встречается, но я считаю это не совсем правильным подходом, так как мне кажется, что интерфейсы предназначены не для этого.
В отличии от наследования, класс может реализовывать несколько интерфейсов. Для этого после ключевого слова implements вы перечисляете через запятую все интерфейсы, которые реализует ваш класс:
Множественное наследование в Java
В Java запрещено множественное наследование и очень часто можно встретить утверждение, что возможность реализовать несколько интерфейсов заменяет множественное наследование. Так вот, я опять таки не соглашусь с этим утверждением.
И то, что вы можете частично заменить множественное наследование с помощью интерфейсов никак не говорит о том, что интерфейсы нужны для этого. Гвоздь микроскопом вы тоже забить можете.
Подробнее про наследование прочитать можно здесь
В Java этой проблемы нет из-за отсутствия множественного наследования, но что если у вас есть два интерфейса, имеющих одно и то же описание метода?
Что тогда? Не нарушает ли это нашу идиллию и равновесие?
Ну а что если мы с помощью default методов попробуем воспроизвести проблему с множественным наследованием?
Разрешение конфликтов реализаций
Определим интерфейс следующим образом:
Как вы думаете, что будет при выполнении следующего кода:
Другими словами, какая из реализаций имеет более высокий приоритет? Правильно, реализация у класса.
Соответственно, вывод будет:
Реализация у класса или у суперкласса всегда имеет более высокий приоритет, чем реализация по умолчанию в интерфейсе.
Теперь напишем еще один интерфейс Two :
И напишем следующий класс:
Еще раз обращу внимание на то, что интерфейсы Two и One связаны между собой.
Как вы думаете, какой результат выполнения кода будет:
Результатом будет.. Барабанная дробь.
Почему так будет понятно, если мы нарисуем как связаны наши интерфейсы и класс:
И это, на мой взгляд, довольно логично. Мы пытаемся вызвать метод и если не находим его реализации у текущего класса идем выше по иерархии.
Наибольшим приоритетом обладает наиболее явный метод, тот до которого ближе всего.
Ну и в тот момент, когда вы уже окончательно запутались и решили завязать с программированием, стоит прояснить последнюю возможную ситуацию.
Достроим ромб и по сути полностью воспроизведем проблему с ромбовидным наследованием.
Диаграмма связи будет в виде:
Будет ли валиден такой код?
Нет, такой код будет не валиден и Java вам явно скажет, что так нельзя, так как каждый из интерфейсов тянет за собой какую-то дефолтную реализацию методов. А в таком случае возникает уже расмотренная проблема ромбовидного наследования.
Поэтому в Java это просто запрещено и комплиятор явно попросит вас реализовать этот метод в классе, реализующем интерфейсы.
Т.е один интерфейс расширяет другой, даже с точки зрения английского языка все выглядит очень логично!
Таким образом, интерфейс Logging расширяет интерфейс Printable и приобретает его константы и методы, кроме статических.
Анонимный класс, реализующий интерфейс
К слову говоря, в Java существует возможность создать анонимный класс, реализующий интерфейс. Это бывает очень удобно тогда, когда объявлять именованный класс не совсем разумно, например, он будет использоваться только один раз. И вместо того, чтобы объявлять класс, придумывать ему имя, реализовывать у него нужный интерфейс и создавать в месте использования объект этого класса можно просто написать так:
С приходом в Java функционального программирования(ФП) стало необходимо как-то объявить функцию. До этого, если вы не забыли, у нас были только методы, которые принадлежали классам.
В Java сделали поддержку ФП максимально по ООП канонам и ввели понятие функционального интерфейса.
Для примера рассмотрим EventHandler :
Этот интерфейс имеет один абстрактный метод, помечен аннотацией и является функциональным интерфейсом. А это дает нам возможность использовать его в виде:
Вот так вот в Java ввели поддержку функций.
Интерфейсы определяют поведение объекта, при этом не выставляя никаких требований к состоянию. Это позволяет абстрагироваться от реализации и ориентироваться только на поведение объекта, на то, что от него можно ждать и как с ним взаимодействовать.
Интефрейсы могут наследоваться друг от друга, один интерфейс может иметь несколько родительских интерфейсов.
С некоторой точки зрения, интерфейсы позвоялют обойти ограничение в множественном наследовании у классов. Помните, что реализация методов в классах имеет более высокий приоритет, чем реализация по умолчанию в интерфейсах.
Всегда держите в уме простое правило:
Использование интерфейса в качестве типа переменной или параметра метода позволяет писать более поддерживаемый, гибкий и понятный код.
Интерфейсы тесно связаны с понятием абстрактного класса, поэтому здесь мы разберем отличия абстрактного класса от интерфейса, а также когда что предпочтительнее использовать.
Организация множественного наследования в Java
В отличие от своего объектно-ориентированного предшественника — языка С++, в языке Java не поддерживается множественное наследование (МН). Но рано или поздно любой программист, использующий парадигму объектно-ориентированного программирования (ООП), сталкивается с необходимостью опираться на методы от разных родительских классов. А опыт применения МН в С++ показал, что это приводит к большему количеству проблем, чем ожидалось. Поэтому в рамках построения парадигмы ООП создатели JAVA подошли с других позиций. Так как же этот вопрос был решён в JAVA?
Для начала рассмотрим как исторически развивался вопрос о проблемах наследования на примере С++.
Множественное наследование позволяет одному дочернему классу иметь несколько родителей. Предположим, что мы хотим написать программу для отслеживания работы учителей. Учитель — это Human. Тем не менее, он также является Сотрудником (Employee).
Множественное наследование может быть использовано для создания класса Teacher, который будет наследовать свойства как Human, так и Employee. Для использования множественного наследования нужно просто указать через запятую тип наследования и второй родительский класс:
Хотя множественное наследование кажется простым расширением одиночного наследования, оно может привести к множеству проблем, которые могут заметно увеличить сложность программ и сделать кошмаром дальнейшую поддержку кода. Рассмотрим некоторые из подобных ситуаций.
Во-первых, может возникнуть неоднозначность, когда несколько родительских классов имеют метод с одним и тем же именем. Например:
При компиляции c54G.getID() компилятор смотрит, есть ли у WirelessAdapter метод getID(). Этого метода у него нет, поэтому компилятор двигается по цепочке наследования вверх и смотрит есть ли этот метод в каком-либо из родительских классов. И здесь возникает проблема — getID() есть как у USBDevice, так и у NetworkDevice. Следовательно, вызов этого метода приведёт к неоднозначности и мы получим ошибку, так как компилятор не будет знать какую версию getID() вызывать.
Тем не менее, есть способ обойти эту проблему. Мы можем явно указать, какую версию getID() следует вызывать:
Хотя это решение довольно простое, но всё может стать намного сложнее, если наш класс будет иметь от 4 родительских классов, которые, в свою очередь, будут иметь свои родительские классы. Возможность возникновения конфликтов имён увеличивается экспоненциально с каждым добавленным родительским классом и в каждом из таких случаев нужно будет явно указывать версии методов, которые следует вызывать, дабы избежать возможности возникновения конфликтов имён.
Во-вторых, более серьёзной проблемой является «алмаз смерти» (или ещё «алмаз обреченности», или «ромб»). Это ситуация, когда один класс имеет 2 родительских класса, каждый из которых, в свою очередь, наследует свойства одного и того же родительского класса. Иллюстративно мы получаем форму алмаза.
Например, рассмотрим следующие классы:
Сканеры и принтеры — это устройства, которые получают питание от розетки, поэтому они наследуют свойства PoweredDevice. Однако копировальный аппарат включает в себя функции как сканеров, так и принтеров.
В этом контексте возникает много проблем, включая неоднозначность при вызове методов и копирование данных PoweredDevice в класс Copier дважды. Хотя большинство из этих проблем можно решить с помощью явного указания, поддержка и обслуживание такого кода может привести к непредсказуемым временным затратам.
Так стоит ли использовать множественное наследование?
Большинство задач, решаемых с помощью множественного наследования, могут быть решены и с использованием одиночного наследования. Многие объектно-ориентированные языки программирования (например: Smalltalk, PHP) даже не поддерживают множественное наследование. Многие, относительно современные языки, такие как Java и C#, ограничивают классы одиночным наследованием, но допускают множественное наследование интерфейсов (об этом поговорим позже). Суть идеи, запрещающей множественное наследование в этих языках, заключается в том, что это лишняя сложность, которая порождает больше проблем, чем удобств.
Многие опытные программисты считают, что множественное наследование в C++ следует избегать любой ценой из-за потенциальных проблем, которые могут возникнуть. Однако всё же остаётся вероятность, когда множественное наследование будет лучшим решением, нежели придумывание двухуровневых костылей.
Правило С++: Используйте множественное наследование только в крайних случаях, когда задачу нельзя решить одиночным наследованием, либо другим альтернативным способом (без изобретения «велосипедов»).
Однако эту проблему можно частично решить с помощью интерфейсов.
Другими словами, для каждого класса в Java может существовать только один родительский класс. Тем не менее в каждом классе можно реализовать произвольное количество интерфейсов.
При этом данный класс будет соответствовать типам всех тех интерфейсов, которые в нем реализованы.
Как видите, с помощью интерфейсов создаются новые типы объектов без их реализации.
Как известно, в абстрактном классе допускается реализация некоторых методов, не объявленных абстрактными. В отличие от них, интерфейсы — это чистой воды шаблоны. С помощью интерфейса можно только определить функциональность, но не реализовать ее (исключения составляют дефолтные методы, которые появились в Java 8).
Чтобы определить интерфейс, используется ключевое слово interface. Например:
Нужно обратить внимание, что интерфейсы — это не классы, хотя и очень похожи на них.
При реализации интерфейса в классе имя интерфейса указывается в объявлении этого класса после ключевого слова implements. После этого процесс реализации интерфейса станет точно таким же, как расширение абстрактного класса, который содержит только абстрактные методы.
Данный интерфейс называется Printable. Интерфейс может определять константы и методы, которые могут иметь, а могут и не иметь реализации. Методы без реализации похожи на абстрактные методы абстрактных классов. Так, в данном случае объявлен один метод, который не имеет реализации.
Все методы интерфейса не имеют модификаторов доступа, но фактически по умолчанию доступ public, так как цель интерфейса — определение функционала для реализации его классом. Поэтому весь функционал должен быть открыт для реализации.
В данном случае класс Book реализует интерфейс Printable. При этом надо учитывать, что если класс применяет интерфейс, то он должен реализовать все методы интерфейса, как в случае выше реализован метод print. Потом в методе main мы можем объект класса Book и вызвать его метод print. Если класс не реализует какие-то методы интерфейса, то такой класс должен быть определен как абстрактный, а его неабстрактные классы-наследники затем должны будут эти методы реализовать.
В тоже время мы не можем создавать объекты интерфейсов, поэтому следующий код не будет работать:
Одним из преимуществ использования интерфейсов является то, что они позволяют добавить в приложение гибкости. Например, в дополнение к классу Book определим еще один класс, который будет реализовывать интерфейс Printable:
Класс Book и класс Journal связаны тем, что они реализуют интерфейс Printable. Поэтому мы динамически в программе можем создавать объекты Printable как экземпляры обоих классов:
Интерфейсы в преобразованиях типов
Интерфейс — это ссылочный тип. Например, так как класс Journal реализует интерфейс Printable, то переменная типа Printable может хранить ссылку на объект типа Journal:
И если мы хотим обратиться к методам класса Journal, которые определены не в интерфейсе Printable, а в самом классе Journal, то нам надо явным образом выполнить преобразование типов:
Методы по умолчанию
Ранее до JDK 8 при реализации интерфейса мы должны были обязательно реализовать все его методы в классе. А сам интерфейс мог содержать только определения методов без конкретной реализации. В JDK 8 была добавлена такая функциональность как методы по умолчанию. И теперь интерфейсы кроме определения методов могут иметь их реализацию по умолчанию, которая используется, если класс, реализующий данный интерфейс, не реализует метод. Например, создадим метод по умолчанию в интерфейсе Printable:
Метод по умолчанию — это обычный метод без модификаторов, который помечается ключевым словом default. Затем в классе Journal нам необязательно этот метод реализовывать, хотя мы можем его и переопределить:
Статические методы
Начиная с JDK 8 в интерфейсах доступны статические методы — они аналогичны методам класса:
Чтобы обратиться к статическому методу интерфейса также, как и в случае с классами, пишут название интерфейса и метод:
Приватные методы
По умолчанию все методы в интерфейсе фактически имеют модификатор public. Однако начиная с Java 9 мы также можем определять в интерфейсе методы с модификатором private. Они могут быть статическими и нестатическими, но они не могут иметь реализации по умолчанию.
Подобные методы могут использоваться только внутри самого интерфейса, в котором они определены. То есть к примеру нам надо выполнять в интерфейсе некоторые повторяющиеся действия, и в этом случае такие действия можно выделить в приватные методы:
Константы в интерфейсах
Кроме методов в интерфейсах могут быть определены статические константы:
Множественная реализация интерфейсов
Если нам надо применить в классе несколько интерфейсов, то они все перечисляются через запятую после слова implements:
Наследование интерфейсов
Интерфейсы, как и классы, могут наследоваться:
При применении этого интерфейса класс Book должен будет реализовать как методы интерфейса BookPrintable, так и методы базового интерфейса Printable.
Вложенные интерфейсы
Как и классы, интерфейсы могут быть вложенными, то есть могут быть определены в классах или других интерфейсах. Например:
При применении такого интерфейса нам надо указывать его полное имя вместе с именем класса:
Использование интерфейса будет аналогично предыдущим случаям:
Интерфейсы как параметры и результаты методов
И также как и в случае с классами, интерфейсы могут использоваться в качестве типа параметров метода или в качестве возвращаемого типа:
Метод read() в качестве параметра принимает любой объект, реализующий интерфейс Printable, поэтому в этот метод мы можем передать как объект Book, так и объект Journal.
Метод createPrintable() возвращает объект, реализующий Printable, поэтому также мы можем возвратить как объект Book, так и Journal.
Давайте предположим, что множественное наследование было реализовано в Java. В этом случае, мы могли бы иметь иерархию классов, как на изображении ниже.
Давайте создадим абстрактный суперкласс SuperClass с методом doSomething(), а также два класса ClassA, ClassB
А теперь давайте создадим класс ClassC, который наследует классы ClassA и ClassB
Обратите внимание, что метод test() вызывает метод суперкласса doSomething()
Это приводит к неопределенности: компилятор не знает, какой метод суперкласса выполнить из-за ромбовидной формы (выше на диаграмме классов). Это и называют проблемой ромба — и это основная причина почему Java не поддерживает множественное наследование классов.
Множественное наследование в интерфейсах
МН не поддерживается в классах, но оно поддерживается в интерфейсах и единый интерфейс может наследовать несколько интерфейсов, ниже простой пример.
Обратите внимание, что в обоих интерфейсах объявлен такой же метод, а теперь посмотрим, что с этого получится:
И это отличный выход, потому что интерфейсы только объявляют методы, а фактическая реализация будет сделана в конкретных классах, которые реализуют интерфейсы, так что нет никакой возможности двусмысленно трактовать множественное наследование в интерфейсе. Все усложняется, если некий класс реализует более одного (скажем, два) интерфейса, а они реализуют один и тот же самый метод по умолчанию. Какой из методов унаследует класс? Ответ — никакой. В таком случае класс должен реализовать метод самостоятельно (напрямую, либо унаследовав его от другого класса).
Теперь давайте посмотрим на код ниже:
Используем Композицию (Composition)?
Так что же делать, если мы хотим использовать метод methodA() класса ClassA и метод methodB() класса ClassB в ClassC?
Решение заключается в использовании композиции. Ниже представлена версия класса ClassC с использованием композиции:
Так что же использовать: Композицию или Наследование?
Одна из лучших практик программирования на Java гласит «Используйте композицию чаще наследования». Давайте рассмотрим этот подход:
Предположим, у нас есть суперкласс и подкласс:
Код выше компилируется и работает нормально, но что будет, если реализация класса ClassC изменяется, как показано ниже:
Обратите внимание, что метод test() уже существует в подклассе, но тип возвращаемого отличается, теперь ClassD не будет компилироваться, и если вы используете какую-то IDE, то вам будет предложено изменить тип возвращаемого значения на тип суперкласса или подкласса.
Теперь представьте себе ситуацию, когда у нас есть несколько уровней наследования класса, однако суперкласс не контролируется нами. В этом случае мы не будет иметь выбора, кроме как изменить сигнатуру метода нашего подкласса или его имя, чтобы удалить ошибку компиляции. Также мы должны внести изменения во все места, где наш метод подкласса использовался.
Указанная проблема никогда не произойдет с композицией, поэтому это делает её предпочтительней, чем наследование.
Еще одна проблема с наследованием в том, что мы предоставляем все методы суперкласса клиенту, и если наш суперкласс не правильно спроектирован и есть дыры в безопасности, то даже если мы позаботимся о правильной реализации нашего подкласса, мы все равно получаем проблемы, которые достались нам от суперкласса.
Композиция помогает нам контролировать доступ к методам суперкласса, в то время как наследование не обеспечивает никакого контроля методов суперкласса. Это тоже одна из основных преимуществ композиции перед наследованием в Java.
Еще одно преимущество композиции в том, что она обеспечивает гибкость в вызове методов. Ниже приведен хороший пример использования композиции:
Результат выполнения этой программы:
Эта гибкость в вызове методов не доступна в наследовании.
При использовании композиции легко проводить модульное тестирование, потому что мы знаем, что все методы не зависят от суперкласса. В то время как при наследовании, мы в значительной степени зависим от суперкласса и не знаем какие методы будут использоваться, поэтому мы должны проверить все методы суперкласса. А это дополнительная работа, которая никому не нужна.
В идеале мы должны использовать наследование только тогда, когда «is-a» отношение справедливо для суперкласса и подкласса во всех случаях, в противном случае мы должны использовать композицию.
Константин Кишкин, после окончания базового курса, июль 2020