Перейти к основному содержимому

Иерархия наследования и преобразование типов

В прошлой главе говорилось о преобразованиях объектов простых типов. Однако с объектами классов все происходит немного по-другому. Допустим, у нас есть следующая иерархия классов:

public class Program {

public static void main(String[] args) {
Person tom = new Person("Tom");
tom.display();
Person sam = new Employee("Sam", "Oracle");
sam.display();
Person bob = new Client("Bob", "DeutscheBank", 3000);
bob.display();
}
}

// класс человека
class Person {

private String name;

public String getName() {
return name;
}

public Person(String name) {
this.name = name;
}

public void display() {
System.out.printf("Person %s \n", name);
}
}

// класс сотрудника некоторой компании
class Employee extends Person{

private String company;

public Employee(String name, String company) {
super(name);
this.company = company;
}

public String getCompany() {
return company;
}

public void display() {
System.out.printf("Employee %s works in %s \n", super.getName(), company);
}
}

// класс клиента банка
class Client extends Person {

private int sum; // Переменная для хранения суммы на счете
private String bank;

public Client(String name, String bank, int sum) {
super(name);
this.bank = bank;
this.sum = sum;
}

public void display() {
System.out.printf("Client %s has account in %s \n", super.getName(), bank);
}

public String getBank() {
return bank;
}

public int getSum() {
return sum;
}
}

В этой иерархии классов можно проследить следующую цепь наследования: Object (все классы неявно наследуются от типа Object) -> Person -> Employee/Client.

Преобразование типов в языке Java

Суперклассы обычно размещаются выше подклассов, поэтому на вершине наследования находится класс Object, а в самом низу Employee и Client.

Объект подкласса Person также представляет объект суперкласса. Поэтому в программе мы можем написать следующим образом:

Object tom = new Person("Tom");
Object sam = new Employee("Sam", "Oracle");
Object kate = new Client("Kate", "Т-Банк", 2000);
Person bob = new Client("Bob", "Т-Банк", 3000);
Person alice = new Employee("Alice", "Яндекс");

Это так называемое восходящее преобразование (от подкласса внизу к суперклассу вверху иерархии) или upcasting. Такое преобразование осуществляется автоматически.

Обратное не всегда верно. Например, объект Person не всегда является объектом Employee или Client. Поэтому нисходящее преобразование или downcasting от суперкласса к подклассу автоматически не выполняется. В этом случае нам надо использовать операцию преобразования типов.

Object sam = new Employee("Sam", "Oracle");

// нисходящее преобразование от Object к типу Employee
Employee emp = (Employee) sam;
emp.display();
System.out.println(emp.getCompany());

В данном случае переменная sam приводится к типу Employee. И затем через объект emp мы можем обратиться к функционалу объекта Employee.

Мы можем преобразовать объект Employee по всей прямой линии наследования от Object к Employee.

Примеры нисходящих преобразований:

Object kate = new Client("Kate", "Т-Банк", 2000);
((Person) kate).display();

Object sam = new Employee("Sam", "Oracle");
((Employee) sam).display();

Но рассмотрим еще одну ситуацию:

Object kate = new Client("Kate", "Т-Банк", 2000);
Employee emp = (Employee) kate;
emp.display();

// или так
((Employee) kate).display();

В данном случае переменная типа Object хранит ссылку на объект Client. Мы можем без ошибок привести этот объект к типам Person или Client. Но при попытке преобразования к типу Employee мы получим ошибку во время выполнения. Так как kate не представляет объект типа Employee.

Оператор instanceof​

В примере выше мы явно видим, что переменная kate - это ссылка на объект Client, а не Employee. Однако нередко данные приходят извне, и мы можем точно не знать, какой именно объект эти данные представляют. Соответственно возникает большая вероятность столкнуться с ошибкой. И перед тем, как провести преобразование типов, мы можем проверить, а можем ли мы выполнить приведение с помощью оператора instanceof:

Object kate = new Client("Kate", "Т-Банк", 2000);
if (kate instanceof Employee) {
Employee employeeKate = (Employee) kate;
employeeKate.display();
} else {
System.out.println("Conversion is invalid");
}

Выражение kate instanceof Employee проверяет, является ли переменная kate объектом типа Employee. Но так как в данном случае явно не является, то такая проверка вернет значение false, и преобразование не сработает.

А выражение kate instanceof Client возвратило бы true:

Object kate = new Client("Kate", "Т-Банк", 2000);
if (kate instanceof Client) {
Client clientKate = (Client) kate;
clientKate.display();
} else {
System.out.println("Conversion is invalid");
}

Следует отметить, что начиная с версии Java 16 мы можем упростить преобразование типов следующим образом:

Object kate = new Client("Kate", "Т-Банк", 2000);
if (kate instanceof Client clientKate) {
clientKate.display();
} else {
System.out.println("Conversion is invalid");
}

Выражение kate instanceof Client clientKate проверяет, представляет ли переменная kate класс Client, и если представляет (то есть оператор instanceof возвращает true), то создает переменную clientKate типа Client. И в дальнейшем мы можем использовать эту переменную clientKate и производить с ней различные операции.