Абастрактные классы и методы, интерфейсы

Помимо обычных классов, в Java бывают абстрактные классы (abstract class) и интерфейсы (interface). Рассмотрим способы их использования на примере абстрактного класса AbstractList, наследующихся от него классов ArrayList и LinkedList, а также интерфейса Iterator, входящих в состав стандартной библиотеки Java

Диаграмма классов

Абстрактные классы (abstract class)

Основное отличие абстрактных классов от простых - это то, что нельзя создавать объект абстрактного класса. То есть нельзя написать

AbstractList a = new AbstractList();

однако можно создать переменную абстрактного типа:

AbstractList a = new ArrayList();

Отличие объявления абстрактного класса от простого заключается в слове abstract:

public abstract class AbstractList {
  …
}

Внутри же абстрактный класс может быть устроен так же, как и обычный.

Как нам известно, ArrayList и LinkedList - это две разные реализации списка - на основе массива и на основе двусвязного списка. В этих классах есть много методов с одинаковой сигнатурой и назначением - например,

ArrayList:	public Iterator iterator();
LinkedList:	public Iterator iterator();

В обоих случаях он должен возвращать итератор. Однако устройство этого метода сильно зависит от структуры класса, и мы не можем просто унаследовать его. Поскольку ArrayList не знает, как устроены его дети, метод iterator() в нем ничего не делает и мог бы выглядеть просто как

AbstractList:
public Iterator iterator() {
  return null;
}

Однако в языке Java для этого случая предусмотрена специальная запись:

AbstractList: public abstract Iterator iterator();

Модификатор abstract у метода означает, что этот метод не определен и вызывать его нельзя, но он должен быть перекрыт во всех наследниках класса. При наследовании (если наследник не абстрактный) от абстрактного класса, компилятор выдает ошибку, если не перекрыты все абстрактные методы абстрактного родителя. Помимо более короткой записи, это также является полезным свойством абстрактных методов.

Порой бывает удобно определять в абстрактном классе конструкторы и методы. Например, в абстрактном классе стандартной библиотеки AbstractSelector, определен конструктор и несколько методов. Мы можем унаследовать от него свой класс, перекрыть абстрактные методы и успользовать уже определенные в родителе.

Интерфейсы (interface)

Интерфейс — это «очень абстрактный класс». Он очень похож на абстрактный класс, но есть и существенные отличия. При объявлении интерфейса слово class не пишется вообще, а используется ключевое слово interface. Например:

public interface Iterator {
  …
}

В интерфейсах не может быть конструкторов, методы должны быть только public abstract, а поля - public final. Создавать в интерфейсе поля и методы не public не имеет смысла, поэтому других прав доступа в интерфейсах нет, а public является значением по умолчанию. Аналогично final и abstract являются значениями по умолчанию. Например, интерфейс Iterator выглядит так:

public interface Iterator {
  Object next();
  boolean hasNext();
  void remove();
}

При наследовании класса от интерфейса вместо "extends" используется ключевое слово implements, например

class A implements I {
}

Интерфейс может наследоваться от другого интерфейса (но не от класса). При этом используется ключевое слово extends:

interface I extends J {
}

В стандартной библиотеке есть множество интерфейсов, например

public interface Set
/* 
 * коллекция, в которой не бывает двух одинаковых элементов
 */
public interface Comparable
/*
 * интерфейс, позволяющий сравнивать объекты на больше/меньше/равно.
 * Содержит единственный метод compareTo который должен возвращать
 * отрицательное число, ноль, или положительное число, если текущий объект
 * больше, равен, или меньше сравниваемого.
 */
public interface Cloneable
/*
 * интерфейс, позволяющий создавать клон текущего объекта
 */

В Java (в отличие от некоторых других языков программирования) каждый класс, кроме Object, имеет ровно одного родителя. Такая политика позволяет избежать сложностей, связанных с зависимостями классов, но при этом создает некоторые неудобства.

Например, у нас есть классы со своей структурой наследования, и мы захотели сделать наши объекты Comparable. Это было бы невозможно. Однако в Java есть возможность наследоваться от произвольного числа интерфейсов. Например, возможна такая запись:

public class B extends A implements I1, I2, I3 {}

Интерфейс тоже может наследоваться от нескольких интерфейсов:

public interface I extends I1, I2, I3 {}

Пока для нас единственное отличие интерфейса от абстрактного класса, расширяющее его возможности - множественное наследование. И на данный момент множественное наследование - единственная причина, по которой следует использовать интерфейсы.