В чем разница между интерфейсом и абстрактным классом в ООП?

«В чем разница между интерфейсом и абстрактным классом в ООП?» — вопрос из категории Основы программирования, который задают на 10% собеседований QA Тестировщик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Ключевые различия:

Критерий Интерфейс Абстрактный класс
Реализация методов Только сигнатуры (до Java 8). С Java 8+ могут быть default и static методы. Может содержать как абстрактные, так и полностью реализованные методы.
Состояние (поля) Не может содержать полей экземпляра (только static final константы). Может содержать поля экземпляра с любым модификатором доступа.
Наследование Класс может реализовать множество интерфейсов. Класс может наследовать только один абстрактный класс.
Конструктор Не имеет конструктора. Имеет конструктор (вызывается при создании наследника).
Цель Определение возможностей (что объект умеет делать). Определение сущности и общего состояния для родственных объектов.

Пример на Java:

// Интерфейс — контракт на возможность рисования
interface Drawable {
    void draw(); // Абстрактный метод (сигнатура)
    default void log() { // Реализованный метод (Java 8+)
        System.out.println("Drawing...");
    }
}

// Абстрактный класс — базовая сущность "Фигура"
abstract class Shape {
    protected String color; // Состояние (поле)

    public Shape(String color) { // Конструктор
        this.color = color;
    }

    abstract double area(); // Абстрактный метод

    void setColor(String c) { // Реализованный метод
        this.color = c;
    }
}

// Класс использует и то, и другое
class Circle extends Shape implements Drawable {
    private double radius;

    public Circle(String color, double radius) {
        super(color); // Вызов конструктора абстрактного класса
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a " + color + " circle");
    }
}

Когда что использовать:

  • Интерфейс: Когда нужно определить поведение для разных по иерархии классов (например, Drawable для Circle, Button, Chart).
  • Абстрактный класс: Когда нужно задать общую структуру и код для родственных классов (например, Shape для Circle, Square, Triangle).