Привет! В предыдущих лекциях мы уже несколько раз мельком встречались с таким понятием как наследование. Сегодня мы тоже коснемся этой темы, но тоже не слишком глубоко. Подробная лекция об это еще будет, а сегодня скорее просто посмотрим практические примеры и познакомимся с одним интересным оператором в Java.

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

Итак, что такое, собственно, наследование? instanceof и основы наследования - 1Наследование — это механизм в программировании, в том числе и в Java, который позволяет описать новый класс на основе уже существующего. Класс-наследник при этом получает доступ к полям и методам родительского класса. Зачем это может быть нужно? Ну, например, представь, что тебе нужно создать в программе несколько классов машин: Грузовик, Гоночная, Седан, Пикап и т.д. Даже не приступив к написанию кода, ты точно знаешь, что у этих классов очень много общего: у всех машин есть название модели, год выпуска, объем двигателя, максимальная скорость и т.д. (не говоря уже про то, что у всех них есть колеса и прочие детали). В такой ситуации ты можешь:
  • Создавать эти поля в каждом классе и добавлять их в новые классы машин при их создании
  • Вынести общие для всех машин поля в родительский класс Car, а все классы конкретных типов машин наследовать от Car с помощью слова extends.
Второй вариант, разумеется, гораздо удобнее:
public class Car {

   private String model;
   private int maxSpeed;
   private int yearOfManufacture;

   public Car(String model, int maxSpeed, int yearOfManufacture) {
       this.model = model;
       this.maxSpeed = maxSpeed;
       this.yearOfManufacture = yearOfManufacture;
   }
}

public class Truck extends Car {

   public Truck(String model, int maxSpeed, int yearOfManufacture) {
       super(model, maxSpeed, yearOfManufacture);
   }
}

public class Sedan extends Car {
   public Sedan(String model, int maxSpeed, int yearOfManufacture) {
       super(model, maxSpeed, yearOfManufacture);
   }
}
Как минимум, мы избежали ненужного дублирования кода, а к этому всегда нужно стремиться при написании программ. Кроме того, у нас есть простая и понятная структура классов: общие для всех машин поля вынесены в один класс. Если, например, у грузовиков есть какие-то специфичные поля, которых нет у остальных машин, их можно объявить в классе Truck. То же самое касается и методов. У всех автомобилей есть какое-то общее поведение, которое можно описать: завести авто, газ/тормоз и т.д. Эти общие методы можно вынести в общий класс Car, а специфическое поведения каждого конкретного типа описать в классах-наследниках.
public class Car {

   public void gas() {
       //...газ
   }

   public void brake() {
       //...тормоз
   }
}


public class F1Car extends Car {

   public void pitStop() {

       //...пит-стоп делают только гоночные автомобили
   }

   public static void main(String[] args) {

       F1Car formula1Car = new F1Car();
       formula1Car.gas();
       formula1Car.pitStop();
       formula1Car.brake();
   }
}
Мы вынесли общие методы всех автомобилей в класс Car. А вот в класс F1Car, который описывает гоночные автомобили “Формулы-1”. Пит-стопы (остановки для срочного обслуживания машины) делают только в гонках, поэтому это специфическое поведение мы вынесли в класс-наследник.

Оператор instanceof

Для проверки того, создан ли объект на основе какого-то класса, в Java существует специальный оператор — instanceof. Он возвращает значение true, если проверка показала истинность, или false, если результат был ложным. Давай посмотрим, как он работает на примере наших классов в машинами:
public class Truck extends Car {

   public static void main(String[] args) {

       Truck truck = new Truck();
       System.out.println(truck instanceof Car);
   }
}
Вывод: true Проверка с помощью оператора instanceof вернула true, поскольку у нас объект класса Truck, а все грузовики — это машины. Класс Truck— наследник класса Car, следовательно, все грузовики создаются на основе общего родителя — машины. Обрати внимание на оператор instanceof: он пишется без точки, поскольку это именно оператор, а не метод (“объект instanceof Класс”). Попробуем по-другому:
public static void main(String[] args) {

   Car car = new Car();
   System.out.println(car instanceof Truck);
}
Вывод: false Класс Car и, соответственно, его объект не происходят от класса Truck.Все грузовики — это машины, но не все машины — грузовики. Объекты Car не создаются на основе класса Truck. Еще один пример:
public static void main(String[] args) {

   Car car = new Car();
   Truck truck = new Truck();
   System.out.println(car instanceof Object && truck instanceof Object);
}
Вывод: True Здесь логика тоже проста: все классы в Java, включая те, которые ты создал, происходит от класса Object (хотя ты и не пишешь в них extends Object — этот механизм заложен в них неявно). Зачем это может пригодиться и при каких обстоятельствах? Наиболее распространенное применение оператора instanceof — это переопределение метода equals(). К примеру, вот как реализован метод equals в классе String:
public boolean equals(Object anObject) {
   if (this == anObject) {
       return true;
   }
   if (anObject instanceof String) {
       String anotherString = (String) anObject;
       int n = value.length;
       if (n == anotherString.value.length) {
           char v1[] = value;
           char v2[] = anotherString.value;
           int i = 0;
           while (n-- != 0) {
               if (v1[i] != v2[i])
                       return false;
               i++;
           }
           return true;
       }
   }
   return false;
}
Прежде чем сравнить строку с переданным объектом, метод проверяет: а является ли, собственно, переданный объект строкой? И уж только потом он начинает сравнивать свойства двух объектов. Без этой проверки в метод можно было бы передать любой объект, у которого есть поля value и length, и сравнивать его со строкой, что, конечно, было бы неправильно.