Главное отличие – это семантика. Интерфейсы появились еще до Java, как важная концепция ООП. Смысл интерфейса – некое поведение, описание свойства. Причем если придерживаться
принципа сегрегации интерфейсов, это описание
единственного аспекта поведения.
Класс, даже абстрактный – это комбинация всех свойств
и их реализаций, которыми определяются сущности некоторой категории (собственно, класса).
Отсюда вытекает естественность и необходимость множественного наследования для интерфейсов. Опыт таких языков как C++ показал, что множественное наследование классов не нужно и проблемно (см.
проблема ромбовидного наследования). По факту же обычно нужно всего лишь переиспользование кода, что не относится к ООП и реализуется в некоторых языках «интерфейсами с независимым состоянием» –
примесями.
В Java интерфейс в отличие от абстрактного класса
не может иметь состояния. Реализация поведения же допустима только в двух случаях: для статических методов, и
default
для обычных. Статические методы являются частью всего класса, а не экземпляров. Дефолтная реализация, как говорилось
ранее, добавлена только как хак для сохранения совместимости.
В интерфейсах, как публичных описаниях, не имеют смысла и запрещены непубличные члены. Отсюда синтаксическое отличие: модификатор
public
, как и
abstract
для методов или
static
для полей, можно не писать. Запрещены и модификаторы, несовместимые с
abstract
:
final
,
synchronized
и прочие.
На уровне скомпилированного байткода тоже есть небольшие различия: интерфейс помечается флагом
ACC_INTERFACE
а для класса генерируется конструктор по-умолчанию.
И есть еще одно небольшое отличие. Интерфейс с одним методом можно использовать как
функциональный, и инстанциировать лямбда-выражением. Для абстрактного класса даже с единственным методом такое не сработает.