1. Наследование.
  2. Переопределение метода и скрытие переменных.
  3. это и супер ключевые слова.

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

Наследование - это процесс, при котором один класс может наследовать видимые свойства и методы от другого класса - отношения родитель-потомок между двумя классами (или суперклассом и подклассом).

// in Person.java file
public class Person {
   public String name;
   public String address;
   public int age;
   
   public void walk() {
      System.out.println(name + " is walking.");
   }
}// in Student.java file
public class Student extends Person {
   public static void main(String[] args){
      Student student = new Student();
      student.name = "John Doe";
      student.address = "101 Main St";
      student.age = 22;
      student.walk();
   }
}

В приведенном выше примере класс Student расширяет класс Person, поэтому наш класс Student является дочерним классом класса Person. Класс ученика расширит все видимые (в зависимости от модификаторов доступа переменных и методов) переменные и методы.

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

Хорошим примером является класс java.lang.Object. Класс объекта - это родительский класс для всех классов в java. Java автоматически вводит синтаксис extends Object после каждого объявления класса. Почему каждый класс должен расширяться от класса super java.lang.Object? Итак, из каждого класса в java мы потенциально можем создать объект. Это может быть Person, это может быть Student, или это может быть Car и так далее, и если мы подумаем об этих классах, все они являются объектами. Java хочет дать общее поведение для каждого объекта, который когда-либо будет создан в java. Java.lang.Object имеет 11 методов (Java 8), поэтому каждый класс наследует эти методы.

equals(Object obj)

Это один из методов, которые будут исходить от класса Object. Нам нужен метод equals для сравнения двух объектов одного класса на равенство. Итак, наш суперкласс предоставляет для этого метод равных. По умолчанию он не будет сравнивать два свойства объекта, он будет сравнивать, если две ссылки указывают на один и тот же объект или нет (то же самое, что и ==). Нам нужно переопределить метод equals и написать логику того, как именно мы хотим сравнивать наши объекты.

Хорошо иметь несколько общих методов для всех объектов, потому что другие библиотеки могут предполагать, что для сравнения ваших объектов они могут использовать метод equals. Такая же логика и для других методов.

Мы говорим, что каждый класс расширяет Object, но в этом примере наш класс Student расширяет наш класс Person, а не класс Object. Да, класс Student расширит класс Object с помощью класса Person.

Java допускает только один тип наследования. Несколько классов могут наследовать от одного класса, но один класс не может наследовать несколько классов одновременно.

Переопределение метода и скрытие переменных

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

// in Car.java file
public class Car {
   public void drive() {
      System.out.println("Car is driving");
   }
}
// in BMW.java file
public class BMW extends Car {
   @Override
   public void drive() {
      System.out.println("BMW is driving");
   }
   public static void main(String[] args) {
      BMW bmw = new BMW();
      bmw.drive(); // BMW is driving
   }
}
  • в приведенном выше примере дочерний класс BMW переопределяет метод родительского класса.
  • @Override аннотация проверяет, действительно ли мы переопределяем метод. Если он обнаруживает какие-то проблемы, он выдает ошибку компилятора.

Правила отмены:

  • Имя и номер метода, порядок и тип аргументов должны быть точно такими же, как у родительского метода.
  • Тип возвращаемого значения должен быть таким же или ковариантным с родительским методом.
  • Модификатор доступа должен быть таким же или более видимым, чем родительский метод.
  • Если объявление исключения существует в родительском методе, дочерний метод может иметь тот же тип объявления исключения или меньший тип.

Почему некоторые люди называют переопределение полиморфизмом времени выполнения?

Car car = new BMW();
car.drive(); // BMW is driving

В приведенном выше примере у нас есть Car в качестве ссылочного типа, и он будет решать, какой метод и переменные доступны для этой ссылки. Фактический объект car - BMW. Во время компиляции java думает, что вызовет метод drive() из класса Car, но во время выполнения выясняет, что BMW фактически переопределяет метод, и вызовет метод из BMW

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

Скрытие отличается от переопределения, здесь нет полиморфизма во время выполнения.

// in Car.java file
public class Car {
   public String name = "CAR";
}
// in BMW.java file
public class BMW extends Car {
   public String name = "BMW";
   public static void main(String[] args) {
      BMW bmw = new BMW();
      System.out.println(bmw.name); // BMW

      Car car = new BMW();
      System.out.println(car.name); // CAR
   }
}

это и супер ключевые слова

this ключевое слово используется для ссылки на текущий объект класса.

Ключевое слово super используется для ссылки на членов родительского класса.

// in Car.java file
public class Car {
   public String name = "CAR";
}
// in BMW.java file
public class BMW extends Car {
   public String name = "BMW";
   public void printName() {
      System.out.println(this.name); // BMW
      System.out.println(super.name); // CAR
   }
   public static void main(String[] args) {
      BMW bmw = new BMW();
      bmw.printName();
   }
}
  • Как вы можете видеть в методе printName(), мы используем super, чтобы получить name родительского класса.
  • Аналогично работает с методами.

Для вызова конструктора мы также можем использовать this и конструктор родительского класса super. Давайте посмотрим на примеры вызова пустых конструкторов.

this(); // calls empty constructor of current class
super(); // calls empty constructor of parent class
This article is part of the series of articles to learn Java programming language from Tech Lead Academy:
1. Introduction to programming 
2. OS, File, and File System
3. Working with terminal 
4. Welcome to Java Programming Language
5. Variables and Primitives in Java
6. Methods with Java
7. Java Math Operators and special operators
8. Conditional branching in Java
9. Switch statement in Java
10. Ternary operator in Java
11. Enum in Java
12. String class and its methods in Java
13. Loops in Java
14. Access modifiers in Java
15. Static keyword in Java
16. The final keyword in Java
17. Class and Object in Java
18. Object Oriented Programming in Java
19. OOP: Encapsulation in Java
20. Inheritance in Java
21. Abstraction in Java
22. Polymorphism in Java
23. Overriding vs Overloading in Java
24. OOP Design Principles in Java
25. Array in Java
26. Data Structures with Java
27. Collection framework in Java
28. ArrayList in Java
29. Set in Java
30. Map in Java
31. LocalDate in Java
32. Exception in Java
33. IO in Java
34. Design Patterns
35. Generics in Java
36. Multithreading in java
37. JUnit
38. Big O Notation for coding interviews
39. Top 17 Java coding interview questions for SDET