В прошлой главе говорилось о преобразованиях объектов простых типов. Однако с объектами классов все происходит немного по-другому. Допустим, у нас есть следующая иерархия классов:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom");
tom.print(); // Person Tom
Person sam = new Employee("Sam", "Yandex");
sam.print(); // Employee Sam works in Yandex
Person bob = new Client("Bob", "SberBank");
bob.print(); // Client Bob has account in SberBank
}
}
// класс человека
class Person {
private String name;
String getName() {return name;}
Person(String name){
this.name=name;
}
void print(){
System.out.printf("Person %s \n", name);
}
}
// служащий некоторой компании
class Employee extends Person{
private String company;
String getCompany(){return company;}
Employee(String name, String company) {
super(name);
this.company = company;
}
void print(){
System.out.printf("Employee %s works in %s \n", getName(), company);
}
}
// класс клиента банка
class Client extends Person{
private String bank;
String getBank(){return bank;}
Client(String name, String bank) {
super(name);
this.bank=bank;
}
void print(){
System.out.printf("Client %s has account in %s \n", getName(), bank);
}
}
В этой иерархии классов можно проследить следующую цепь наследования: Object (все классы неявно наследуются от типа Object) -> Person -> Employee|Client.
Суперклассы обычно размещаются выше подклассов, поэтому на вершине наследования находится класс Object, а в самом низу Employee и Client.
Объект подкласса также представляет объект суперкласса. Поэтому в программе мы можем написать следующим образом:
public class Program{
public static void main(String[] args) {
Object tom = new Person("Tom"); // преобразование от Person к Object
Object sam = new Employee("Sam", "Yandex"); // от Employee к Object
Object kate = new Client("Kate", "SberBank"); // от Client к Object
Person bob = new Client("Bob", "VTB"); // от Client к Person
Person alice = new Employee("Alice", "VK"); // от Employee к Person
}
}
Это так называемое восходящее преобразование (от подкласса внизу к суперклассу вверху иерархии) или upcasting. Такое преобразование осуществляется автоматически.
Обратное не всегда верно. Например, объект Person не всегда является объектом Employee или Client. Поэтому нисходящее преобразование или downcasting от суперкласса к подклассу автоматически не выполняется. В этом случае нам надо использовать операцию преобразования типов. Например:
Object sam = new Employee("Sam", "Yandex");
sam.print(); // ! Ошибка
Здесь хотя переменная sam и хранит ссылку на объект типа Employee, но является переменной типа Object. Поэтому при компиляции мы столкнемся с ошибкой - поскольку объект типа Object
не обязательно представляет тип Employee или Person, у него может и не быть метода print().
И в этом случае нам надо применить нисходящее преобразование от типа Object к типу Employee или Person:
Object sam = new Employee("Sam", "Yandex");
// нисходящее преобразование от Object к типу Employee
Employee emp = (Employee)sam;
emp.print();
System.out.println(emp.getCompany());
В данном случае переменная sam приводится к типу Employee. И затем через объект emp мы можем обратиться к функционалу объекта Employee.
Мы можем преобразовать объект Employee по всей прямой линии наследования от Object к Employee.
Примеры нисходящих перобразований:
public class Program{
public static void main(String[] args) {
Object kate = new Client("Kate", "Sberbank");
((Person)kate).print();
Object sam = new Employee("Sam", "Yandex");
((Employee)sam).print();
}
}
Но рассмотрим еще одну ситуацию:
public class Program{
public static void main(String[] args) {
Person kate = new Client("Kate", "Sberbank");
Employee emp = (Employee) kate; // ! Ошибка - java.lang.ClassCastException
emp.print();
// или так
((Employee)kate).print();
}
}
В данном случае переменная kate, которая имеет тип Person, хранит ссылку на объект Client. Мы можем без ошибок привести этот объект к типу Client. Но при попытке преобразования
к типу Employee мы получим ошибку во время выполнения. Так как kate НЕ представляет объект типа Employee.
В примере выше мы явно видим, что переменная kate - это ссылка на объект Client, а не Employee. Однако нередко данные приходят извне, и мы можем точно не знать, какой именно объект эти данные представляют. Соответственно возникает большая вероятность столкнуться с ошибкой. И перед тем, как провести преобразование типов, мы можем проверить, а можем ли мы выполнить приведение с помощью оператора instanceof:
public class Program{
public static void main(String[] args) {
Person kate = new Client("Kate", "Sberbank");
printPerson(kate); // Person is not an employee
Person sam = new Employee("Sam", "Yandex");
printPerson(sam); // Employee works in Yandex
}
static void printPerson(Person person){
if(person instanceof Employee){
Employee empl = (Employee) person;
System.out.println("Employee works in " + empl.getCompany());
}
else{
System.out.println("Person is not an employee");
}
}
}
В методе printPerson выражение person instanceof Employee проверяет, представляет ли параметр person объект типа Employee. И если не является, то такая
проверка вернет значение false, и преобразование не сработает, и выполняется блок else:
else{
System.out.println("Person is not an employee");
}
Если же параметр person представляет тип Employee, то выполняется блко if, где преобразуем в тип Employee и выводим на консоль информацию об объекте:
if(person instanceof Employee){
Employee empl = (Employee) person;
System.out.println("Employee works in " + empl.getCompany());
}