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

Открыта

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

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

Пример:

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

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

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

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

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

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

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

4
Задача
Java Core,  4 уровень,  3 лекция
Недоступна
Набираем код Ӏ 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)
{
  Animal animal = new Tiger();
  doAllAction(animal);

  Animal animal2 = new Cat();
  doAllAction(animal2);

  Animal animal3 = new Animal();
  doAllAction(animal3);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

Комментарии (222)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий вы должны авторизоваться
Дмитрий14 уровень, Санкт-Петербург
вчера, 18:19
Херня какая-то, ничего не понятно где сужение где расширение...
Иван18 уровень, Москва
7 августа, 11:24
Смысл понятен. Но кто, вашего паскаля, ляпнул, что уменьшение количества методов называется РАСШИРЕНИЕМ, а увеличение СУЖЕНИЕМ и теперь этим все пользуются?!!!!
Иван16 уровень, Москва
14 июля, 19:31
Верно ли утверждение: В данном коде, Tiger наследуется от Animal:
class Animal
{
  public void doAnimalActions(){}
}
class Cat extends Animal
{
  public void doCatActions(){}
}
class Tiger extends Cat
{
  public void doTigerActions(){}
}
? Цитата:
Движение вверх по цепочке наследования называется расширением, поскольку оно приводит к более общему типу. Но при этом теряется возможность вызвать методы, которые были добавлены в класс при наследовании.
Значит ли это, что в коде:
Pet cat = new Cat();
public class Cat extends Pet{
public void catHunting(){
System.out.println("I catch mouse!")
}
}
cat.catHunting();
Вызов метода catHunting(); не сработает?
javaSerdj28 уровень, Санкт-Петербург
15 июля, 04:43
Видимость методов обусловлена типом ссылочной переменной. В данном случае у тебя тип ссылочной переменной Pet. Соответственно, если у тебя данного метода в классе Pet нет - то не увидит. Если же он был, то будет работать динамическое связывание - другими словами переопределение метода.
Иван16 уровень, Москва
14 июля, 17:55
Такое ощущение что в голове какой то нереальный пробел и чем его забить я не понимаю..=(((
Павел 16 уровень, Подольск
9 июля, 13:55
да здравствует каша в голове! Система ввода-вывода+приведение типов = 💣🧠💣 Терпение, только терпение.
Igor Tarasov19 уровень, Свободный, Белебей
5 июня, 23:59
Оказалось легче чем я думал (нет)
Денис17 уровень
22 июня, 08:48
А вы пробовали посмотреть результат такого преобразования, но не с числом 4? Впишите, к примеру, 47890 и выполните свою программу. Помещать int в byte - значит отрезать все значения int, которые не влезли в byte. Непредсказуемый результат.
Максим34 уровень, Екатеринбург
24 июня, 08:02
помещать int в byte вы сможете только явным кастом - это почти сужение типа. Потому и просит подтвердить, что результат непредсказуем
Денис17 уровень
24 июня, 08:50
Комментарий, к которому я ответил - удален. Автор понял, что все немного сложнее. Тему кастинга классов знаю, но спасибо, что напомнили.
Владимир Фомичёв18 уровень, Москва
12 мая, 08:39
что-то я не совсем понял смысла этих операций. Animal animal = new Tiger(); Мы (наш код) не всегда знаем, с объектом какого типа мы работаем. но ведь мы создаем объект класса ТИГР, получается, что мы все-таки знаем с объектом какого типа мы работаем.
Dmitry Gorchakov24 уровень, Москва
15 мая, 12:29
ссылка типа ЖИВОТНЫЕ животные = новый объект типа ТИГР; есть два варианта как относиться к объекту ТИГР в данном случае: 1) как к животному (расширение), используя исключительно методы класса ЖИВОТНЫЕ (своего рода обобщение) 2) как к тигру (сужение), используя в том числе методы класса ТИГР (конкретизация). Тут используем явное преобразование Захотел общий список всяких животных - обобщил/расширил, Захотел из списка животных уточнить инфу по конкретной зверушке - сузил
Павел Павличенко34 уровень, Львов
8 мая, 06:24
Получается, что сужать я могу только в диапазоне классов? Скажем от Object в самом кранем левом (верхнем) положении до типа экземпляра класса, который я создал?
Artem17 уровень, Рига
7 мая, 18:10
"Ну, это-то уже ясно. Оказалось легче, чем я думал." прям в точку
Хондамир18 уровень
15 апреля, 18:16
Самая главная информация из лекции - С объектом при таком присваивании ничего не происходит! Меняется только количество методов, которое можно вызвать с помощью конкретной переменной-ссылки.