Отличие и правильность использования абстрактных классов от интерфейсов в Java
Пытаюсь понять чем эти два элемента отличаются. Почитал тут и тут, а также лекцию которую изучаю. Если я правильно понимаю, кроме определения и подключения я вижу следующее возможности:
Абстрактный класс
- нельзя создать экземпляр класса
- должны быть определен хотя бы заголовок метода, следовательно наследник обязан переопределять этот метод
- метод может быть полностью реализован
- наследнике метод можно переопределить и в итоге получится 1 метод
- не фиксированные значения переменных
Интерфейс
- нельзя создать экземпляр интерфейса
- должны быть определены заголовки методов
- метод можно переопределить и в итоге будет 2 метода (хотя зачем нам столько методов, если достаточно одного)
- метод можно определить через использование default
- фиксированные значения переменных
Я понимаю, что интерфейс определяет состояние, а абстрактный класс определяет и состояние и поведение. Почему тогда не пользоваться только одним вариантом, например классом?
Мы можем наследоваться только от одного класса, но от многих интерфейсов. Но почему тогда не создать еще несколько абстрактных классов с наследованием друг от друга и в итоге полученное нечто унаследовать в рабочем классе?
В каких случаях нужно использовать именно то, а не другое?
Есть ли примеры использования в которых именно необходимо использовать тот или иной вариант?
Интересует именно применение в Java.
Дополнение Допустим есть такая структура.
abstract class Figure {
double dim1;
double dim2;
Figure(double a, double b) {
dim1 = a;
dim2 = b;
}
abstract double area();
}
Есть класс треугольника:
class Rectangle extends Figure {
Rectangle(double a, double b) {
super(a, b);
}
double area() {
System.out.println("B области четырехугольника.");
return dim1 * dim2;
}
}
Есть класс квадрата:
class Triangle extends Figure {
Triangle(double a, double b) {
super(a, b);
}
double area() {
System.out.println("B области треугольника.");
return dim1 * dim2 / 2;
}
}
Есть точка входа:
class AbsractAreas {
public static void main(String[] args) {
Rectangle r = new Rectangle(9, 5);
Triangle t = new Triangle(10, 8);
Figure figRef = r;
System.out.println("Площадь равна " + figRef.area());
figRef = t;
System.out.println("Площадь равна " + figRef.area());
}
}
Я могу переделать её в интерфейс? Нужно ли её переделать в интерфейс? Или стоит разделить на две части?
PS: Дополнительный вопрос к интерфейсам. Я знаю, что интерфейс предназначен для защиты работы класса. Но разве недостаточно наложения ограничений на класс (я имею ввиду модификаторы доступа)?
PS PS: Зачем в интерфейс была добавлена возможность определения тела методов?
И вот если я определю в интерфейсе метод например move (int n), а в наследнике мне нужно использовать move (int n, int m). Не проще ли тогда создать абстрактный класс с методом move () и потом переопределять его под нужное количество параметров? Или у имеется возможность для переопределения количества параметров в методе в интерфейсе? Или у нас так и останутся два метода move (int n) и move (int n, int m)?
PS PS PS: Извиняюсь за большое количество вопросов. Просто хотелось бы полностью разобраться с этой темой)
Ответы (3 шт):
Интерфейс определяет поведение, в общем смысле интерфейс рассказывает о том, как работать с классом, который этот интерфейс реализует, к примеру все двери умеют закрываться и открываться и на не важно какая это дверь и как она сделана, главное, что это дверь и у неё есть наличие методов для открытия и закрытия. Интерфейс может использоваться классами, которые вообще никак не связаны с друг другом по смыслу (Comparable как пример). И самое важное множественное наследование.
Абстрактный класс определяет поведение и состояние. Абстрактный класс используют для наследования (т.е. мы имеем тесную связь между классами), с помощью него описывают общие черты для наследников. В нем могут находится конкретные (в интерфейсе тоже с java 8) и статические методы.
PS нашел статью, которая скорее всего поможет лучше понять разницу ссылка
Интерфейс - это, по-большей части, некий признак общего поведения. Например, если у вас объект может быть клонируемым, то среди предков его класса наверняка есть интерфейс Cloneable. Если объект передвигаемый, то - Moveable и так далее. И методы у признаков соответствующие - их цель это обозначить!, а не реализовать поведение объекта класса наследника признака-интерфейса.
Абстрактный класс - это все-таки класс, или "недокласс". То есть сущность, которая такая же как и конкретный класс, но ему чего-то не хватает.
Например, абстрактный класс Shape (Форма) вполне себе сущность/класс, но мы не можем сказать, что именно эта за форма (куб, квадрат, круг, параллелепипед), например, мы не можем сказать как ее отрисовать, или схватить, или изменить, сколько у нее вершин и ребер, поэтому данная сущность вполне может иметь какую-то частичную реализацию, например методы изменения и хранение положения центра формы в пространстве, или на плоскости, но отображение и конкретное поведение такой сущности мы, все-таки, оставим потомкам/наследникам.
Обе конструкции, кроме всего прочего, сами по себе могут быть наследниками таких же интерфейсов и абстрактных классов, если в этом есть архитектурная/смысловая необходимость.
З.Ы. Лучше всего поможет понять разницу - практика! В процессе разработки архитектуры приложения и последующей реализации одни из те же сущности могут стать вначале интерфейсами, а потом классами (и даже абстрактными), а потом наоборот )
Рассмотрим пример, когда нужны именно интерфейсы и нельзя обойтись абстрактными классами.
Это ситуация, когда нам нужно несколько аспектов функциональности объединить в одном классе, и эти аспекты никак не связанны с собой (так что делать абстрактные классы и наследовать их друг от друга - непрактично или даже невозможно).
Поясню на примере. Допустим нам нужна возможности
- создавать копии объектов.
- сравнивать объекты
- сериализировать объекты в массив байт
Для этого можно либо создать интерфейсы типа:
interface Clonable<T> {
T clone();
}
interface Comparable<T> {
int compare(T other);
}
interface Serializable {
byte[] asBytes();
}
Либо - создать абстрактные классы с теми же методами.
Нужно понимать, что эти классы или интерфейсы существуют не в вакууме. Мы предполагаем, что у этих интерфейсов (или классов) есть клиенты, например библиотеки, которые их используют. Например, Serilizable - часть библиотеки для обмена сообщениями по сети, а Comparable - часть библиотеки для работы с коллекциями.
Т.е. где-то существуют функции, которые используют их:
void sort(List<Comparable> list);
Теперь когда мы хотим создать конкретный класс который должен уметь и копировать себя, и чтобы его можно было сравнить и чтобы можно было сериализировать, с интерфейсами все просто - делаем класс и реализуем все три интерфейса.
Если же мы использовали абстрактные классы, то возникают проблемы:
- чтобы иметь все три возможности, какой из классов должен от кого наследоваться?
- что если у нас все три абстрактных класса в разных библиотеках, которые вообще никак не связаны (делаются разными разработчиками, которые друг о друге не знают, и вообще не знают, что кому то может понадобится использовать класс из другой библиотеки),
- даже если мы выбрали какой-то способ упорядочить абстрактные классы в иерархию, а нам нужна только частичная функциональность и не нужна функциональность, которую дает класс из корня иерархии, как быть?
Одним словом - для этой задачи абстрактные классы не годятся. Главная причина - это отсутствие множественного наследования в java. Множественное наследование могло бы решить эту проблему, но за счет введения новых проблем (присущих самому множественному наследованию).
Интерфейсы (особенно начиная с java8) могут заменить абстрактные классы (в совокупности с другими мерами). Но иногда использование абстрактного класса может быть более практично.
Допустим, мы создаем библиотеку для сохранения объектов в БД. Можно сделать интерфейс для объекта, который можно сохранять:
interfact Persistable<K> {
K getId();
}
Однако в этом случае в каждом конкретном классе, который реализует Persistable, придется определять поле id. Если таких классов много, то может быть более практично сделать Persistable абстрактным классом и тогда он сможет хранить состояние:
abstract class Persistable<K> {
private K id;
public K getKey() {
return id;
}
Это конечно простой пример и тут это неоправданно, но если таких полей больше, плюс есть определенные условия, которые должны выполняться относительно этих полей, для того, чтоб объект Persistable был правильно сконструирован (например что id != null), то тогда делать классы-наследники ответственными за это может быть неудобно, т.к. одна и та же логика будет находиться в нескольких местах и удобней с точки зрения поддержки иметь ее в одно месте.
Это может помочь выявить ошибки в наследниках, когда наследник (не по злому умыслу) неправильно реализует интерфейс, на более ранних стадиях.
Конечно у наследования реализации и использования абстрактных классов есть минусы, которые нужно учитывать, и взвешивать за и против в конкретном случае.