Наследование

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

Одним из ключевых аспектов объектно-ориентированного программирования является наследование. С помощью наследования можно расширить функционал уже имеющихся классов за счет добавления нового функционала или изменения старого. Например, имеется следующий класс Person, описывающий отдельного человека:

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

И, возможно, впоследствии мы захотим добавить еще один класс, который описывает сотрудника предприятия - класс Employee:

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

Однако так как этот класс реализует тот же функционал, что и класс Person, поскольку сотрудник - это также и человек, то было бы целесообразнее не определять повторно один и тот же функционал, а как-то передать этот функционал из класса Person в класс Employee. И для этого можно унаследовать класс Employee от класса Person:

class Employee extends Person { }  

Чтобы унаследовать один класс от другого, надо использовать после имени класса-наследника ключевое слово extends, после которого идет имя базового класса. Для класса Employee базовым является Person, и поэтому класс Employee наследует все те же поля и методы, которые есть в классе Person.

Таким образом, наследование реализует отношение is-a (является). То есть объект класса Employee также является объектом класса Person. В этом отношении класс Employee еще называют производным классом (а также дочерним классом, классом-наследником, подклассом) а класс Person - базовым классом (а также родительским классом или суперклассом).

Затем мы можем использовать функционал базового класса (Person), обращаясь к объекту производного класса (Employee):

public class Program{ 
       
    public static void main (String args[]){
           
        Person tom = new Person();
        tom.name = "Tom";
        tom.print();

        Employee bob = new Employee();
        bob.name = "Bob";
        bob.print();
    }
}

class Person {
     
    String name;

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

class Employee extends Person{}  

То есть в данном случае благодаря наследованию у объекта класса Employee мы можем обратиться и к полю name, и к методу print, хотя они определены в классе Person.

Наследование конструкторов

Если в базовом классе определены конструкторы, то в конструкторе производного классы необходимо вызвать один из конструкторов базового класса с помощью ключевого слова super, в который передаются значения для параметров конструкторв:

super(аргументы);

Например, определим к классе Person конструктор:

class Person {
     
    private String name;

    Person(String name){

        this.name = name;
    }

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

class Employee extends Person{

    // надо вызвать конструктор базового класса
    Employee(String name){
        super(name);  // вызываем конструктор базового класса
    }
}  

Здесь конструктор класса Person принимает один параметр. Поэтому в классе Employee в конструкторе нужно вызвать конструктор класса Person. То есть вызов super(name) будет представлять вызов конструктора класса Person.

При вызове конструктора после слова super в скобках идет перечисление передаваемых аргументов. Таким образом, установка имени сотрудника делегируется конструктору базового класса.

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

Использование классов:

public class Program{ 
       
    public static void main (String args[]){
           
        Person tom = new Person("Tom");
        tom.print();

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

    Person(String name){

        this.name = name;
    }

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

class Employee extends Person{

    Employee(String name){
        super(name);
    }
}  

Стоит отметить, что при определении конструктора в производном классе до версии Java 25 вызов конструктора суперкласса должны был выполняться ДО всех остальных инструкций. Например:

Employee(String name){
    super(name);   // До Java 25 вызов конструктора мог быть только в самом начале

    // все остальные инструкции
    System.out.println("Employee created");
}

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

Employee(String name){

    System.out.println("Employee creation started");

    super(name);   // Начиная с Java 25 можно поместить вызов конструктора в любое место
}

Расширение базового класса

Производный класс не только наследует функционал от базового класса, но и также может расширять его - добавлять свои поля и методы. Например, класс Employee представляет работника некоторой компании, и соответственно нам бы хотелось хранить в нем данные об этой компании:

public class Program{ 
       
    public static void main (String args[]){
           
        Employee bob = new Employee("Bob", "Google");
        bob.print();    // Name: Bob
        bob.work();     // Company: Google
    }
}

class Employee extends Person{

    private String company;

    Employee(String name, String company){
        super(name); 
        this.company = company;
    }

    // выводим данные о компании
    void work(){
        System.out.println("Company: " + company);
    }
}  

class Person {
     
    private String name;

    Person(String name){

        this.name = name;
    }

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

В данном случае класс Employee добавляет поле company, которое хранит место работы сотрудника, а также метод work для вывода названия компании.

Доступ к функциональности базового класса

Производный класс имеет доступ ко всем методам и полям базового класса (даже если базовый класс находится в другом пакете) кроме тех, которые определены с модификатором private. Так в примере выше мы сделали поле name в классе Person приватным (определенным с модификатором private). Поэтому класс Employee не имеет к нему доступа:

class Employee extends Person{

    private String company;

    Employee(String name, String company){
        super(name); 
        this.company = company;
    }

    // выводим данные о компании
    void work(){
        System.out.println("Name: " + name);  // ! Ошибка: так нельзя - переменная name - приватная
        System.out.println("Company: " + company);
    }
}  

При попытке скомпилировать этот класс мы столкнемся с ошибкой, так как в методе work идет обращение к переменной name базового класса Person, которая является приватной.

Определение полей приватными представляет стандартную практику лучше инкапсулировать и защитить данные от некорретного вмешательства извне. Однако бывают случаи, когда требуется открыть доступ к компоненту (полю или методу) базового класса только для производных классов. В этом случае компонент класса объявляется с помощью модификатора protected. Например, если суперкласс Person объявляет поле name с модификатором protected, а не приватным, то методы класса Employee могут обращаться к нему напрямую:

public class Program{ 
       
    public static void main (String args[]){
           
        Employee bob = new Employee("Bob", "Google");
        bob.work();     // Name: Bob
                        // Company: Google
    }
}

class Employee extends Person{

    private String company;

    Employee(String name, String company){
        super(name); 
        this.company = company;
    }

    // выводим данные о компании
    void work(){
        System.out.println("Name: " + name);  // так можно - переменная name - protected
        System.out.println("Company: " + company);
    }
}  
class Person {
     
    protected String name;

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

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

В Java поле с модификатором protected доступно любому классу в том же пакете. Поэтому для надлежащей инкапсуляции нам надо поместить класс Person в отдельный пакет. В этом случае поля и методы с модификатором protected будут доступны только классу Employee и другим унаследованным классам.

Переопределение методов

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

public class Program{ 
       
    public static void main (String args[]){
           
        Employee bob = new Employee("Bob", "Google");
        bob.print();
    }
}

class Employee extends Person{

    private String company;

    Employee(String name, String company){
        super(name); 
        this.company = company;
    }
    @Override
    void print(){
        System.out.println("Company: " + company);
    }
}  
class Person {
     
    private String name;

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

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

Здесь производный класс Employee переопределяет метод print из базового класса. Перед переопределяемым методом указывается аннотация @Override. Данная аннотация в принципе необязательна.

При переопределении метода он должен иметь уровень доступа не меньше, чем уровень доступа в базовом класса. Например, если в базовом классе метод имеет модификатор public, то и в производном классе метод должен иметь модификатор public.

В классе Employee реализация метода print выводит значение поля company на консоль. Но что делать, если мы хотим, чтобы переопределенный метод print также выводил и значение поля name из базового класса? Напрямую в классе Employee мы не можем обращаться к полю name, потому что оно приватное. Однако мы можем вызвать в производном классе реализацию метода базового класса с помощью слова super:

super.метод(аргументы)

Например:

public class Program{ 
       
    public static void main (String args[]){
           
        Employee bob = new Employee("Bob", "Google");
        bob.print();        // Name: Bob
                            // Company: Google
    }
}

class Employee extends Person{

    private String company;

    Employee(String name, String company){
        super(name); 
        this.company = company;
    }
    @Override
    void print(){
        super.print();  // вызов реализации метожжа print из класса Person
        System.out.println("Company: " + company);
    }
}  
class Person {
     
    private String name;

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

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

Здесь в методе print в классе Employee помощью ключевого слова super мы обращаемся к реализации метода print из базового класса, которая выводит имя:

super.print();
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850
Morty Proxy This is a proxified and sanitized view of the page, visit original site.