Курс

"Программирование Java"

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

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

Наследование — это отношение между классами, при котором класс использует структуру или поведение другого класса.

Наследование — принцип объектно-ориентированного программирования, в рамках которого возможно описание новых классов на основе уже существующих таким образом, что свойства (в Java — поля) и методы родителя будут присутствовать и в наследниках. Родительский класс в данном случае принято называть суперклассом.

 

Имеется класс User, описывающий отдельного человека:

class User {

      

    private String name;

    public String getName(){ 

      return name; 

    }

     

    public User(String name){

      

        this.name=name;

    }

   

    public void display(){

          

        System.out.println("Name: " + name);

    }

}

 

В дальнейшем мы захотим добавить еще один класс, который описывает сотрудника предприятия - класс Employee. Так как этот класс реализует тот же функционал, что и класс User, поскольку сотрудник - это также и человек, то было бы рационально сделать класс Employee производным (наследником, подклассом) от класса User, который, в свою очередь, называется базовым 

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

 

class Employee extends Person{}

 

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

 

Для класса Employee базовым является User, и поэтому класс Employee наследует все те же поля и методы, которые есть в классе User.

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



 

public class Main {

      

    public static void main(String[] args) {

             

        User alex = new User("Alex");

        alex.display();

        Employee mike = new Employee("Mike");

        mike.display();

    }

}

 

class User {

      

 

    private String name;

    public String getName(){ return name; }

      

    public User (String name){

      

        this.name = name;

    }

   

    public void display(){

          

        System.out.println("Name: " + name);

    }

}

 

class Employee extends User {

 

}

 

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

 

При этом производный класс также может добавлять свои поля и методы:

public class Main {

      

    public static void main(String[] args) {

             

        Employee mike = new Employee("Mile", "Yandex");

        mike.display();

        mike.work();     

    }

}

class User {

      

    private String name;

    public String getName(){ return name; }

      

    public User(String name){

      

        this.name=name;

    }

   

    public void display(){

          

        System.out.println("Name: " + name);

    }

}

class Employee extends User {

  

    private String company;

      

    public Employee(String name, String company) {

      

        super(name);

        this.company=company;

    }

    public void work(){

        System.out.printf("%s works in %s \n", getName(), company);

    }

}

 

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

 

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

 

Класс User имеет конструктор, который принимает один параметр. Поэтому в классе Employee в конструкторе нужно вызвать конструктор класса User

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

При этом вызов конструктора базового класса должен идти в самом начале в конструкторе производного класса.

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

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

 

public class Main {

      

    public static void main(String[] args) {

             

        Employee mike = new Employee("Mike", "Yandex");

        mike.display();

                        

    }

}

class User {

      

    private String name;

    public String getName(){ return name; }

      

    public User(String name){

      

        this.name=name;

    }

   

    public void display(){

          

        System.out.println("Name: " + name);

    }

}

class Employee extends User {

  

    private String company;

      

    public Employee(String name, String company) {

      

        super(name);

        this.company=company;

    }

    @Override

    public void display(){

          

        System.out.printf("Name: %s \n", getName());

        System.out.printf("Works in %s \n", company);

    }

}

 

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

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

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

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

class Employee extends User {

      

    private String company;

      

    public Employee(String name, String company) {

      

        super(name);

        this.company=company;

    }

    @Override

    public void display(){

          

        super.display();

        System.out.printf("Works in %s \n", company);

    }

}

 

С помощью ключевого слова super мы также можем обратиться к реализации методов базового класса.

 

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

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

 

Например:

public final class User {}

 

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

class Employee extends User {}

 

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

public class User {

 

    //................

     

    public final void display(){

         

        System.out.println("Имя: " + name);

    }

}

 

В этом случае класс Employee не сможет переопределить метод display()

Абстрактные классы

Кроме обычных классов в программе могут содержаться абстрактные классы

Абстрактными называются классы, которые имеют абстрактные методы

Абстрактными называются те методы, которые не имеют реализации в классе. После круглых скобок следует не блок описания метода, а точка с запятой. Перед именем метода указывается при этом ключевое слово abstract.

Абстрактные классы призваны предоставлять базовый функционал для классов-наследников. А производные классы уже реализуют этот функционал

Отличным примером реализации абстрактного класса является система геометрических фигур. 

В реальности есть общее понятие геометрической фигуры, но общее представление отсутствует, есть круг, прямоугольник, квадрат, но просто фигуры нет. Однако же и круг, и прямоугольник имеют что-то общее и являются фигурами.

// абстрактный класс фигуры

abstract class Figure{

     

    float x; // x-координата точки

    float y; // y-координата точки

  

    Figure(float x, float y){

         

        this.x=x;

        this.y=y;

    }

 

    // абстрактный метод для получения периметра

    public abstract float getPerimeter();

 

    // абстрактный метод для получения площади

    public abstract float getArea();

}

 

// производный класс прямоугольника

class Rectangle extends Figure

{

    private float width;

    private float height;

  

    // конструктор с обращением к конструктору класса Figure

    Rectangle(float x, float y, float width, float height){

         

        super(x,y);

        this.width = width;

        this.height = height;

    }

     

    public float getPerimeter(){

         

        return width * 2 + height * 2;

    }

     

    public float getArea(){

         

        return width * height;

    }

}