← Назад к вопросам

Что такое переопределение?

1.3 Junior🔥 211 комментариев
#ООП#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Переопределение (Override) в Java

Переопределение (Override) — это механизм полиморфизма в Java, который позволяет подклассу предоставить собственную реализацию метода, определенного в суперклассе. Это одна из ключевых концепций объектно-ориентированного программирования.

Определение и назначение

Переопределение — это когда подкласс создает новую версию метода, унаследованного от суперкласса, с той же сигнатурой (имя, параметры, возвращаемый тип).

// Суперкласс
public class Animal {
    public void makeSound() {
        System.out.println("Издает звук");
    }
}

// Подкласс переопределяет метод
public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Собака лает: Гав!");
    }
}

// Подкласс переопределяет метод
public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Кошка мяукает: Мяу!");
    }
}

Полиморфизм в действии

public void animalSounds() {
    Animal animal1 = new Dog(); // Полиморфная переменная
    Animal animal2 = new Cat();
    
    animal1.makeSound(); // Выведет: Собака лает: Гав!
    animal2.makeSound(); // Выведет: Кошка мяукает: Мяу!
    
    // Динамическое связывание (Dynamic Binding)
    // Вызванный метод определяется в runtime, а не compile time
}

Правила переопределения

1. Сигнатура метода должна совпадать ТОЧНО

public class Parent {
    public void method(int x) {}
}

public class Child extends Parent {
    // ПРАВИЛЬНО - переопределение
    @Override
    public void method(int x) {
        System.out.println("Child method");
    }
    
    // НЕПРАВИЛЬНО - перегрузка (overloading), не переопределение
    public void method(String s) {}
    
    // НЕПРАВИЛЬНО - другой возвращаемый тип
    // public int method(int x) {} // Ошибка компиляции
}

2. Модификатор доступа не может быть более ограничивающим

public class Parent {
    public void publicMethod() {}
    protected void protectedMethod() {}
    private void privateMethod() {} // НЕ может быть переопределен
}

public class Child extends Parent {
    // ПРАВИЛЬНО - public может остаться public
    @Override
    public void publicMethod() {}
    
    // ПРАВИЛЬНО - protected → public (расширение доступа)
    @Override
    public void protectedMethod() {}
    
    // НЕПРАВИЛЬНО
    // @Override
    // protected void publicMethod() {} // Ошибка! Сужение доступа
}

3. Возвращаемый тип может быть подтипом (Covariant Return Types)

class Parent {
    public Number getValue() {
        return 10;
    }
}

class Child extends Parent {
    // ПРАВИЛЬНО - Integer является подтипом Number
    @Override
    public Integer getValue() {
        return 20;
    }
}

4. Аннотация @Override

public class Child extends Parent {
    // Аннотация помогает избежать ошибок при переопределении
    @Override
    public void myMethod() {
        // Если вы опечатались в имени метода,
        // компилятор выведет ошибку
    }
}

Отличие от Overloading (перегрузки)

public class Calculator {
    // Перегрузка (Overloading)
    public int add(int a, int b) {
        return a + b;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
    
    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

public class Parent {
    public void print(int x) {
        System.out.println("Parent: " + x);
    }
}

public class Child extends Parent {
    // Переопределение (Override) - ТА ЖЕ сигнатура
    @Override
    public void print(int x) {
        System.out.println("Child: " + x);
    }
    
    // Перегрузка (Overloading) - ДРУГАЯ сигнатура
    public void print(String s) {
        System.out.println("Child String: " + s);
    }
}

Практические примеры

Пример 1: Интерфейсы и реализация

public interface Shape {
    double getArea();
    String getDescription();
}

public class Circle implements Shape {
    private double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public String getDescription() {
        return "Круг с радиусом " + radius;
    }
}

public class Rectangle implements Shape {
    private double width, height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double getArea() {
        return width * height;
    }
    
    @Override
    public String getDescription() {
        return "Прямоугольник " + width + "x" + height;
    }
}

Пример 2: Иерархия классов

public abstract class Vehicle {
    public abstract void start();
    public abstract void stop();
}

public class Car extends Vehicle {
    @Override
    public void start() {
        System.out.println("Машина заводится двигателем");
    }
    
    @Override
    public void stop() {
        System.out.println("Машина останавливается");
    }
}

public class Bicycle extends Vehicle {
    @Override
    public void start() {
        System.out.println("Велосипед готов к поездке");
    }
    
    @Override
    public void stop() {
        System.out.println("Велосипед останавливается");
    }
}

Динамическое связывание (Runtime Polymorphism)

public void driveVehicle(Vehicle vehicle) {
    vehicle.start();  // Вызов переопределенного метода
    // Какой именно start() будет вызван, определяется в runtime
    vehicle.stop();
}

public void test() {
    Vehicle car = new Car();
    Vehicle bike = new Bicycle();
    
    driveVehicle(car);  // Выведет "Машина заводится..."
    driveVehicle(bike); // Выведет "Велосипед готов..."
}

Ошибки при переопределении

public class Parent {
    public int getValue() {
        return 10;
    }
}

public class Child extends Parent {
    // ОШИБКА 1: Другой возвращаемый тип
    // public void getValue() {} // Ошибка компиляции
    
    // ОШИБКА 2: Другая сигнатура параметров
    // public int getValue(int x) {} // Это перегрузка, не переопределение
    
    // ОШИБКА 3: Сужение модификатора доступа
    // private int getValue() {} // Ошибка компиляции
    
    // ПРАВИЛЬНО
    @Override
    public int getValue() {
        return 20;
    }
}

Когда использовать переопределение

  1. Полиморфизм - разные классы имеют разные реализации одного метода
  2. Абстрактные методы - подклассы должны реализовать интерфейс/абстрактный класс
  3. Специализация - подкласс уточняет или расширяет поведение суперкласса
  4. Замена поведения - полностью переопределить логику метода

Ключевые моменты

  • Переопределение включает динамическое связывание
  • Метод выбирается в runtime, основываясь на типе объекта
  • Должна совпадать сигнатура метода (имя и параметры)
  • Возвращаемый тип может быть подтипом (covariant)
  • Модификатор доступа не может быть более ограничивающим
  • Используйте @Override для проверки корректности