Professor Hans Noodles
41 уровень

instanceof и основы наследования

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

Наследование 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

Для проверки того, создан ли объект на основе какого-то класса, в 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, и сравнивать его со строкой, что, конечно, было бы неправильно.
Комментарии (152)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Kirill Уровень 18
22 ноября 2021
Про реализацию метода equals в классе String: Для пояснений можно взять следующий код: (можно в IDE его скопировать, чтоб посмотреть вывод)

        String first = "привет";
        String linkToFirst = first;
        String second = new String ("привет");
        System.out.println(first.equals(linkToFirst));
        System.out.println(first.equals(second));
Первый System.out.println выведет true. И дойдет проверка только до первого if. Мы вызываем у объекта "first", типа String, метод equals, передавая в качестве параметра другую строку "linkToFirst", которая, как мы видим, просто ссылается на тот же самый объект. Оператор "this" ссылается на объект, который вызвал этот метод. В нашем случае - это "first" вызвал метод "equals", и в качестве передаваемого параметра отправил туда second. Когда мы написали в коде this, то подразумевали - "first". Т.е. Это можно прочитать как:

public boolean equals(Object anObject) { 
   if (first == second) {
Так почему же мы попадем только в первый if? Потому что при использовании оператора == при сравнении объектов, сравниваются не сами объекты, а только ссылки на них. А у нас как раз такая история - мы ссылаемся на один и тот же объект, получаем return true, и вывод в консоль true.

public boolean equals(Object anObject) { 
   if (this == anObject) {
       return true;
   }
Не дает всё сразу опубликовать, поэтому второй System.out.println(first.equals(second)); ниже
Kirill Уровень 18
22 ноября 2021
Второй System.out.println выведет также true, но проверка уже пойдет по всему методу, ибо первый if выдаст false. Ведь second не ссылается на first привет, а ссылается на новый объект
 new String 
строки "привет".

if (anObject instanceof String) {  //Проверяем, является ли строкой, 
// переданный в метод объект (second) 
       String anotherString = (String) anObject; //если да, то приводим 
// переданный объект к типу String 
       int n = value.length;  // value - это массив символов*, объекта String, 
// который вызывает метод equals. 
// value.length - его длина. *- на самом деле это массив байтов, и каждый 
// символ может быть представлен в виде 1, 2, 3 или даже 4 байтами, 
// но сути это не меняет
       if (n == anotherString.value.length) { // сравниваем длину массива 
// символов текущего объекта, с переданным т.е. длину first и second)
           char v1[] = value;
           char v2[] = anotherString.value;
           int i = 0;
           while (n-- != 0) {  // далее мы в цикле уменьшаем количество 
// заходов в while и поочередно сравниваем между собой элементы (символы) 
// массивов исходного "first " и  переданного 
// "second" (берется один и тот же индекс массива - "i"). 
// Как только они не совпадут - false и выход из цикла. 
// Если цикл завершился без преждевременного окончания, 
// значит объекты идентичны. 
               if (v1[i] != v2[i])
                       return false;
               i++;
           }
           return true;
       }
   }
   return false;
В общем на ваш суд, если что-то непонятно или некорректно - поправлю.
fFamous Уровень 51, Санкт-Петербург
27 июля 2021
Как я и думал, value: /** The value is used for character storage. */ private final char value[]; http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/lang/String.java Но насколько я понял, в последних версиях java хранит String не в массиве char, а в байтах.
Wiun Уровень 16, Житомир, Украина
18 апреля 2021

if (this == anObject)
что здесь обозначает this?
Стас Уровень 15, Москва
15 марта 2021
Последний пример я вообще не понял. Дошел до строки N6 , а дальше мрак.
Эндер Уровень 15, Москва, Россия
16 января 2021
А в чём смысл первого примера кода? Ведь приватные члены класса не наследуется! Это наивысший уровень инкапсуляции!
🦔 Виктор Уровень 20, Москва, Россия Expert
10 ноября 2020
Всё хорошо, кроме последнего примера, остаётся ощущение недосказанности, расписали бы его подробнее с комментариями каждого действия. Но, в любом случае, спасибо за труды!
Равиль Ганиев Уровень 27
6 ноября 2020
нашел ответ на свой вопрос!!! Оператор instanceof нельзя использовать, если нет связи между сравниваемым объектом и типом, с которым он сравнивается
Равиль Ганиев Уровень 27
6 ноября 2020
у меня вопрос! сравниваю ( car instanceof Cat) выдает такую ошибку: "Incompatible conditional operand types" почему? прчему не выдает false?
Алексей Уровень 30, Phonky Town
3 октября 2020
Эта лекция мне очень понравилась! Особенно последний пример!