Приведение типов. Расширение и сужение

Открыта

— Привет, Амиго! Тема сегодняшней лекции – расширение и сужение типов. С расширением и сужением примитивных типов ты познакомился уже давно. На 10 уровне. Сегодня мы расскажем, как это работает для ссылочных типов, т.е. для объектов классов.

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

Пример:

Код Описание
class Pet
{
public void doPetActions();
}class Cat extends Pet
{
public void doCatActions();
}class Tiger extends Cat
{
public void doTigerActions();
}
Тут мы видим три объявленных класса: животное, кот и тигр. Кот наследуется от Животного. А Тигр от Кота.
public static void main(String[] args)
{
Tiger tiger = new Tiger();
Cat cat = new Tiger();
Pet pet = new Tiger();
Object obj = new Tiger();
}
Объект класса Tiger всегда можно спокойно присвоить переменной с типом класса-родителя. Для класса Tiger – это Cat, Pet и Object.

Теперь рассмотрим, что же такое расширение и сужение типов.

Если в результате присваивания мы двигаемся по цепочке наследования вверх (к типу Object), то это — расширение типа (оно же — восходящее преобразование или upcasting), а если вниз, к типу объекта, то это — сужение типа (оно же — нисходящее преобразование или downcasting).

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

Код Описание
public static void main(String[] args)
{
Object obj = new Tiger();
Pet pet = (Pet) obj;
Cat cat = (Cat) obj;
Tiger tiger = (Tiger) pet;
Tiger tiger2 = (Tiger) cat;
}
При сужении типа, нужно использовать оператор преобразования типа, то есть мы выполняем явное преобразование.

При этом Java-машина выполняет проверку, а действительно ли данный объект унаследован от Типа, к которому мы хотим его преобразовать.

Такое небольшое нововведение уменьшило количество ошибок в преобразовании типов в разы, и существенно повысило стабильность работы Java-программ.

16
Задача
Java Core,  4 уровень,  3 лекция
Недоступна
Набираем код
Внимание! Объявляется набор кода на JavaRush. Чтобы поучаствовать, включите режим повышенной внимательности, немного разомните пальцы, затем — расслабьте их и… набирайте код в соответствующем окошке. На самом деле это довольно полезное занятие, а не пустая трата времени, как может показаться.
Код Описание
public static void main(String[] args)
{
Object obj = new Tiger();
if (obj instanceof Cat)
{
Cat cat = (Cat) obj;
cat.doCatActions();
}}
Еще лучше – использовать проверку instanceof
public static void main(String[] args)
{
Pet pet = new Tiger();
doAllAction(pet);

Pet pet2 = new Cat();
doAllAction(pet2);

Pet pet3 = new Pet();
doAllAction(pet3);
}

public static void doAllAction(Pet pet)
{
if (pet instanceof Tiger)
{
Tiger tiger = (Tiger) pet;
tiger.doTigerActions();
}

if (pet instanceof Cat)
{
Cat cat = (Cat) pet;
cat.doCatActions();
}

pet.doPetActions();
}
И вот почему. Смотрим на пример слева.

Мы (наш код) не всегда знаем, с объектом какого типа мы работаем. Это может быть как объект того же типа, что и переменная (Pet), так и любой тип-наследник (Cat, Tiger).

Рассмотрим метод doAllAction. Он корректно работает в независимости от того, объект какого типа в него передали.

Т.е. он корректно работает для всех трех типов Pet, Cat, Tiger.

public static void main(String[] args)
{
Cat cat = new Tiger();
Pet pet = cat;
Object obj = cat;
}
Тут мы видим три присваивания. Все они являются примерами расширения типа.

Оператор преобразования типа тут не нужен, так как не нужна проверка. Ссылку на объект всегда можно сохранить в переменную любого его базового типа.

— О, на предпоследнем примере все стало понятно. И для чего нужна проверка, и для чего нужно преобразование типов.

— Надеюсь, что так. Хочу обратить твое внимание на следующую вещь:

С объектом при таком присваивании ничего не происходит! Меняется только количество методов, которое можно вызвать с помощью конкретной переменной-ссылки.

Например, переменная класса Cat позволяет вызывать методы doPetActions & doCatActions, и ничего не знает о методе doTigerActions, даже если ссылается на объект класса Tiger.

— Ну, это-то уже ясно. Оказалось легче, чем я думал.

Комментарии (99)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий вы должны авторизоваться
Vladislav Polekha17 уровень, Penza
7 марта, 15:20
ищите внизу коментарий левомеколя.
NewBuy16 уровень, Санкт-Петербург
25 февраля, 18:26
И вот почему. Смотрим на пример слева. Этот пример как-то связан с ООП?
Максим19 уровень, Минск
13 февраля, 20:15
В первом примере ошибка: вместо объявления тела методов классов Pet, Cat, Tiger в виде фигурных скобок стоит точка с запятой. Если это так, то метод, также как и его класс, должен быть объявлен абстрактным, соответственно от такого класса нельзя создать его экземпляр.
Кирилл Манжос15 уровень
26 февраля, 07:30
Я думаю что они хотели чтоб мы суть уловили, хотя это ошибка грубая и поставить вместо ';' '{}' труда не составило бы)
VIKTOR NEZHELSKIY27 уровень
10 февраля, 16:04
Итак: Тигр = это и кошка, и животное, и вообще какой-то объект (Object). Назвав его так мы употребляем всё более широкие понятия. По-этому Object obj = new Tiger(); - расширение типа всегда сработает для более широких понятий тигра (родителей его класса). Но применяя расширенное понятие тигра, например употребив к объекту понятие "животное", истина будет только если этот объект был по рождению как минимум животным, хоть кошкой, хоть тигром. Но и дерево тоже объект и к нему понятие "животное" не работает (его класс не относится к животным). По-этому необходимо сужение типа уточнением: Pet pet = (Pet) obj; Или для начала проверить, действительно-ли объект животное: if (obj instanceof Pet). - правда ли, что объект можно назвать животным. if (obj instanceof Cat). - правда ли, что объект можно назвать кошкой. if (obj instanceof Tiger). - правда ли, что объект можно назвать тигром. Для объекта, который тигр по происхождению это будет истиной во всех трёх проверках, а для объекта дерева по происхождению - ни разу истиной не будет.
Dmitry Golyshkin22 уровень, Москва
14 января, 08:23
Главное не начать читать комменты, если понял лекцию.
Sergo17 уровень, Санкт-Петербург
14 января, 19:36
и правда не стоит
Виталий Злобин16 уровень, Новосибирск
11 января, 10:46
В практике такое применяется или эта лекция для понимания: что может произойти и откуда ноги растут если...?
Sergo17 уровень, Санкт-Петербург
14 января, 19:33
Ну я например подошел к пониманию лямбд. Расширения класса до объекта типа интерфейса и следовательно мы все реализации методов берем из интерфейса, а так как у него нет реализации, кроме дефолтных, мы получаем необходимость прописать ее самому
5 января, 12:06
Т.е. при расширении кол-во методов уменьшается, а при сужении - увеличивается
Pavlic Morozov (pashok09i)28 уровень, Екатеринбург
24 ноября 2018, 17:05
Объясните пжл на пальцах, не могу до конца проникнуться Движение вверх по цепочке наследования называется расширением, поскольку оно приводит к более общему типу. Но при этом теряется возможность вызвать методы, которые были добавлены в класс при наследовании.
Andrii Gorshunov36 уровень
24 ноября 2018, 21:56
Например есть иерархия сверху-вниз: Object > Pet > Cat. Если расширим изначально созданный объект Cat catExample = new Cat(); до переменной типа Object: Object obj = (Object) cat; то по ссылке obj можно будет вызвать только методы классов Object, но нельзя вызвать методы родительского класса Pet. Исполнение методов, если они будут у объекта (их реализация) будет выполнена как у объекта Cat. Тогда как при нормальном создании (без ап/даун-кастингов) объекта дочернего класса Cat (class Cat extends Pet), где родительский класс Pet, объекту дочернего класса(то есть Cat) доступны методы родительского класса.
cat_shepherd17 уровень, Харьков
27 декабря 2018, 22:17
то по ссылке obj можно будет вызвать только методы классов Object и Cat === уверен?))) вызови-ка методы класса Cat)))
Andrii Gorshunov36 уровень
27 декабря 2018, 23:05
Исправил. Список методов будет доступен согласно переменной типа, однако индивидуальное исполнение принадлежит объекту.
Сергей С18 уровень, Минск
10 марта, 17:18
Object obj = (Object) catExample;
Макс20 уровень, Киев
6 ноября 2018, 22:40
ЧТО Я ПОНЯЛ 1. Сужение типа (давнкастинг) ─ это когда переменной дочернего класса присваивается переменная родительского класса. При этом необходимо явно указывать тип дочернего класса (в скобках слева от переменной родительского класса).
Smartphone smartphone = new IPhoneSE();
IPhoneSE iphoneSE = (IPhoneSE) smartphone;
2. Расширение типа (апкастинг) ─ это когда переменной родительского класса присваивается переменная дочернего класса. 3. Оператор "instanceof" возвращает true, если объект сравнивается: - с типом своего класса - с типом любого из своих классов-родителей - с интерфейсом, который имплементирует либо свой класс, либо любой из классов-родителей своего класса
Макс20 уровень, Тверь
3 октября 2018, 11:25
Не могу понять "if (pet instanceof Tiger)", как переменная типа Pet может соответствовать дочернему классу Tiger. Вот если бы было наоборот, тогда было бы понятно, а так никак не доходит..как такое может быть
Alisandro15 уровень
5 октября 2018, 05:07
Дело в том, что проверяется не переменная, а объект, который в ней содержится. А в переменной родительского типа могут содержаться объекты дочерних типов.
fleek25 уровень
5 октября 2018, 10:38
Я для себя запись "if (pet instanceof Tiger)" читаю так: является ли объект, на который ссылается переменная pet объектом класса Tiger. И тогда все становится понятным.
Den17 уровень, Одесса
19 ноября 2018, 11:49
если pet типа Pet, то этот if и не отработает