JavaRush /Курсы /Модуль 2. Java Core /Абстрактные классы в Java на конкретных примерах

Абстрактные классы в Java на конкретных примерах

Модуль 2. Java Core
2 уровень , 1 лекция
Открыта
Привет! Поговорим об абстрактных классах в Java. Абстрактные классы в Java на конкретных примерах - 1

Почему классы называют «абстрактными»

Ты наверняка помнишь, что такое «абстракция» — мы это уже проходили :) Если вдруг подзабыл — не страшно, вспомним: это принцип ООП, согласно которому при проектировании классов и создании объектов необходимо выделять только главные свойства сущности, и отбрасывать второстепенные. Например, если будем проектировать класс SchoolTeacher — школьный учитель — вряд ли понадобится характеристика «рост». Действительно: для преподавателя эта характеристика не важна. Но вот если мы будем создавать в программе класс BasketballPlayer — игрок в баскетбол — рост станет одной из главных характеристик. Так вот, абстрактный класс — это максимально абстрактная, о-о-о-чень приблизительная «заготовка» для группы будущих классов. Эту заготовку нельзя использовать в готовом виде — слишком «сырая». Но она описывает некое общее состояние и поведение, которым будут обладать будущие классы — наследники абстрактного класса.

Примеры абстрактных классов Java

Рассмотрим простой пример с машинами:

public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;
  
   public abstract void gas();

   public abstract void brake();

   public String getModel() {
       return model;
   }

   public void setModel(String model) {
       this.model = model;
   }

   public String getColor() {
       return color;
   }

   public void setColor(String color) {
       this.color = color;
   }

   public int getMaxSpeed() {
       return maxSpeed;
   }

   public void setMaxSpeed(int maxSpeed) {
       this.maxSpeed = maxSpeed;
   }
}
Вот так выглядит самый простой абстрактный класс. Как видишь, ничего особенного :) Для чего он может нам понадобиться? Прежде всего, он максимально абстрактно описывает нужную нам сущность — автомобиль. Слово abstract находится здесь недаром. В мире не существует «просто машин». Есть грузовики, гоночные автомобили, седаны, купе, внедорожники. Наш абстрактный класс — это просто «чертеж», по которому мы позже будем создавать классы-автомобили.

public class Sedan extends Car {
  
   @Override
   public void gas() {
       System.out.println("Седан газует!");
   }

   @Override
   public void brake() {
       System.out.println("Седан тормозит!");
   }
  
}
Это во многом похоже на то, о чем мы говорили в лекциях про наследование. Только там у нас класс Car и его методы не были абстрактными. Но у такого решения есть целый ряд минусов, которые в абстрактных классах исправлены. Первое и главное — экземпляр абстрактного класса создать нельзя:

public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Ошибка! Класс Car является абстрактным!
   }
}
Эта «фишка» была реализована создателями Java специально. Еще раз, для запоминания: абстрактный класс — это просто чертеж для будущих «нормальных» классов. Тебе же не нужны экземпляры чертежа, правильно? Вот и экземпляры абстрактного класса создавать не надо :) А если бы класс Car не был абстрактным, мы легко могли бы создавать его объекты:

public class Car {

   private String model;
   private String color;
   private int maxSpeed;
  
   public void gas() {
       // какая-то логика
   }

   public  void brake() {
       // какая-то логика
   }
}


public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Все ок, машина создалась
   }
}
Теперь у нас в программе появилась какая-то непонятная машина — не грузовик, не гоночная, не седан, а вообще непонятно что. Та самая «просто машина», которых в природе не существует. Тот же пример можно привести с животными. Представь, если бы в твоей программе появились объекты Animal — «просто животное». Какого оно вида, к какому семейству относится, какие у него характеристики — непонятно. Было бы странно увидеть его в программе. Никаких «просто животных» в природе не существует. Только собаки, кошки, лисы, кроты и другие. Абстрактные классы избавляют нас от «просто объектов». Они дают нам базовое состояние и поведение. Например, у всех машин должна быть модель, цвет и максимальная скорость, а еще они должны уметь газовать и тормозить. Вот и все. Это — общая абстрактная схема, дальше ты уже сам проектируешь нужные тебе классы. Обрати внимание: два метода в абстрактном классе тоже обозначены как abstract, и они вообще не реализованы. Причина та же: абстрактные классы не создают «поведения по умолчанию» для «просто машин». Они просто говорят, что должны уметь делать все машины. Впрочем, если поведение по умолчанию тебе все-таки нужно, методы в абстрактном классе можно реализовать. Java этого не запрещает:

public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public void gas() {
       System.out.println("Газуем!");
   }

   public abstract void brake();
  
   //геттеры и сеттеры
}


public class Sedan extends Car {

   @Override
   public void brake() {
       System.out.println("Седан тормозит!");
   }

}

public class Main {

   public static void main(String[] args) {

       Sedan sedan = new Sedan();
       sedan.gas();
   }
}
Вывод в консоль: “Газуем!” Как видишь, мы реализовали в абстрактном классе один метод, а второй не стали. В итоге поведение нашего класса Sedan разделилось на две части: если вызвать у него метод gas(), он «подтянется» из родительского абстрактного класса Car, а метод brake() мы переопределили в классе Sedan. Получилось очень удобно и гибко. Но теперь наш класс не такой уж и абстрактный? Ведь у него, по факту, половина методов реализована. На самом деле — и это очень важная особенность — класс является абстрактным, если хотя бы один из его методов является абстрактным. Хоть один из двух, хоть один из тысячи методов — без разницы. Мы можем даже реализовать все методы и не оставить ни одного абстрактного. Будет абстрактный класс без абстрактных методов. В принципе, это возможно, и компилятор не выдаст ошибок, но лучше так не делать: слово abstract потеряет смысл, а твои коллеги-программисты сильно удивятся, увидев такое :/ При этом, если метод помечен словом abstract, каждый класс-наследник должен его реализовать или быть объявленным как абстрактный. Иначе компилятор выбросит ошибку. Разумеется, каждый класс может наследоваться только от одного абстрактного класса, так что в плане наследования разницы между абстрактными и обычными классами нет. Неважно, наследуемся мы от абстрактного класса или от обычного, класс-родитель может быть только один.

Почему в Java нет множественного наследования классов

Мы уже говорили, что в Java нет множественного наследования, но так толком и не разобрались почему. Давай попробуем сделать это сейчас. Дело в том, что если бы в Java было множественное наследование, дочерние классы не могли бы определиться, какое именно поведение им выбрать. Допустим, у нас есть два класса — Toster и NuclearBomb:

public class Toster {
  
  
 public void on() {

       System.out.println("Тостер включен, тост готовится!");
   }
  
   public void off() {

       System.out.println("Тостер выключен!");
   }
}


public class NuclearBomb {

   public void on() {

       System.out.println("Взрыв!");
   }
}
Как видишь, у обоих есть метод on(). В случае с тостером он запускает приготовление тоста, а в случае с ядерной бомбой — устраивает взрыв. Ой :/ А теперь представь, что ты решил (уж не знаю, с чего вдруг!) создать что-то среднее между ними. И вот он твой класс — MysteriousDevice! Этот код, разумеется, нерабочий, и мы приводим его просто в качестве примера «а как оно могло бы быть»:

public class MysteriousDevice extends Toster, NuclearBomb {

   public static void main(String[] args) {
      
       MysteriousDevice mysteriousDevice = new MysteriousDevice();
       mysteriousDevice.on(); // И что же здесь должно произойти? Мы получим тост, или ядерный апокалипсис?
   }
}
Давай посмотрим, что у нас получилось. Загадочное устройство происходит одновременно и от Тостера, и от Ядерной Бомбы. У обоих есть метод on(), и в результате непонятно, какой из методов on() должен срабатывать у объекта MysteriousDevice, если мы его вызовем. Объект никак не сможет этого понять. Ну и в качестве вишенки на торте: у Ядерной Бомбы нет метода off(), так что если мы не угадали, отключить устройство будет нельзя. Абстрактные классы в Java на конкретных примерах - 2 Именно из-за такой «непонятки», когда объекту неясно, какое поведение он должен выбрать, создатели Java отказались от множественного наследования. Впрочем, ты узнаешь, что классы Java реализуют множество интерфейсов.
Комментарии (3)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Руслан Никитин Уровень 77
24 июля 2024
при множественном наследовании не только проблемы с методами, но так же и с совпадением имен переменных.
Олег Уровень 106 Expert
28 июня 2024
тоже не понимаю, почему нельзя было добавить, например, внесение цифр в метод, чтобы было понятно, какой класс-родитель выбирать. К примеру: mysteriousDevice.onOne() - выбирался бы Toster. mysteriousDevice.onTwo() - выбирался бы NuclearBomb.
Тарас Ш. Уровень 72
25 сентября 2022
И всё же, на моём уровне, мне кажется надуманной причина неиспользования мультинаследования в языке. В случае с интерфейсами может возникнуть такая же ситуация, однако там эта проблема решена. Ты просто обязан заоверрайдить метод, присутствующий в обоих интерфейсах, или/и прямо указать использующийся интерфейс, по типу Interface1.someMethod() или Interface2.someMethod() Соответственно, именно этой проблемы потенциально не существует.