Запрет наследования и переопределения методов

Последнее обновление: 24.09.2025

Хотя наследование очень интересный и эффективный механизм, но в некоторых ситуациях его применение может быть нежелательным. И в этом случае можно запретить наследование с помощью ключевого слова final. Например:

final class Person { }

Если бы класс Person был бы определен таким образом, то следующий код был бы ошибочным и не сработал, так как мы тем самым запретили наследование:

class Employee extends Person{ }  // ! Ошибка - унаследовать от класса Person нельзя

Кроме запрета наследования можно также запретить переопределение отдельных методов. Например, в примере выше переопределен метод print(), запретим его переопределение:

class Person {
     
    private String name;

    Person(String name){ this.name = name;  }

    // запрет переопределения
    final void print(){
         
        System.out.println("Name: " + name);
    }
}

В этом случае класс-наследник не сможет переопределить метод print:

class Employee extends Person{

    private String company;

    Employee(String name, String company){
        super(name); 
        this.company = company;
    }
    // ! Ошибка - переопределить метод нельзя
    void print(){
        super.print();
        System.out.println("Company: " + company);
    }
}  

Запрет наследования класса или переопределения метода с помощью final может быть полезным, если мы хотим гарантировать, что семантика класса или метода не будет изменена в производном классе, то есть класс или метод будет действовать ровно так, как он был изначально определен. Поскольку если внутри базового класса вызываются его методы, то переопределение этих методов в производном классе может нарушить стандартную и ожидаемую работу этого класса. Рассмотрим небольшой пример:

public class Program{ 
       
    public static void main (String args[]){

        Employee bob = new Employee("Bob", "Google");
    }
}
class Person {
     
    private String name;

    Person(String name){ 

        this.name = name;  
        print();
    }

    void print(){
         
        System.out.println("Name: " + name);
    }
}

class Employee extends Person{

    private String company;

    Employee(String name, String company){

        super(name); 
        this.company = company;
    }
    @Override
    void print(){
        super.print();
        System.out.println("Company: " + company);
    }
}  

В конструкторе класса Person вызывается метод print() для вывода информации о созданном объекте. Но в классе Employee переопределяем этот метод, чтобы также вывести значение поля company.

Поскольку в конструкторе Employee мы уже вызываем конструктор базового класса Person, нам не нужно второй раз вызывать метод print:

Employee(String name, String company){

    super(name);   // здесь вызывается метод print
    this.company = company;
}

Однако посмотрим на консольный вывод программы:

Name: Bob
Company: null

Конструктор базового класса Person, где вызывается метод print, отработал ДО установки поля company. В итоге при выводе на консоль выводится некорректная информация.

Вызов метода базового класса в конструкторе - это одна из распространенных причин запрета переопределения метода. И в этом случае метод print в классе Person мы можем определить как final:

public class Program{ 
       
    public static void main (String args[]){

        Employee bob = new Employee("Bob", "Google");
    }
}
class Person {
     
    private String name;

    Person(String name){ 
        this.name = name;  
        printPerson();
    }
    // запрет переопределения
    final void printPerson(){
         
        System.out.println("Name: " + name);
    }
}

class Employee extends Person{

    private String company;

    Employee(String name, String company){

        super(name);  // в этой точке в базовом классе вызывается метод printPerson 

        this.company = company;
        printEmployee();
    }

    void printEmployee(){
        System.out.println("Company: " + company);
    }
}  

В этом случае мы получим корректный вывод:

Name: Bob
Company: Google

Стоит отметить, что проблема выше была связана прежде всего с тем, что вызов конструктора базового класса выполнялся до остального кода (установки поля company). И в до версии Java 25 мы могли вызывать конструктор базового класса только в самом начале конструктоора производного класса до всего остального кода:

Employee(String name, String company){

    super(name); 
    this.company = company;
}

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

Employee(String name, String company){

    this.company = company;
    super(name); 
}

Конкретно эта деталь в данном случае бы решила проблему, тем не менее в более сложных программах нам надо учитывать все возможные зависимости, которые могут повлять на создание объекта базового класса. Поэтому в конструкторе лучше вызывать только методы с модификаторами final или private.

Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850