JavaRush /Курсы /Java Core /Приведение типов. Расширение и сужение

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

Java Core
4 уровень, 3 лекция
Открыта

— Привет, Амиго! Тема сегодняшней лекции – расширение и сужение типов. С расширением и сужением примитивных типов ты познакомился уже давно. На 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.

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

Комментарии (426)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
TimJohnzz Уровень 22
21 сентября 2026
Короче, если максимально кратко. На восхождение по наследованию можем делать без приведения типов, потому что в наследнике содержатся методы родителя, и Java позволяет нам сохранять в ссылку родителя обьект наследника, так как любой наследник может "отыграть" роли родителя. Но превратить потом обратно этого наследника из родителя "под прикрытием" можно только через приведение типа, так как иначе, JVM будет ругаться, и желательно делать это через instanceof, чтобы случайно не превратить родителя настоящего в наследника.ХАХА Animal animal = new Dog() <- так можем, так как Собака - это наследник и может "притвориться" животным Dog dog = (Dog) animal <- тут обратно "переодеваем" нашу собаку-родителя в просто собаку. А проверка нам нужна, чтобы случайно не сделать так: Animal animal = new Animal(); Dog dog2 = (Dog) animal; <- здесь мы из изначального родителя, пытаемся переодеть в наследника, что нельзя, так как родитель уже прожил жизнь и не может иметь методы наследника :/ поэтому делаем проверочку через instanceof
Euraks Уровень 33
14 октября 2026
Причина такого поведения в компиляторе. Во время компиляции нужно связать объект и ссылку на объект

A extends B;

A a = new B;
компилятор производит статическое связывание объекта А с динамическим объектом B и на 100% может быть уверен что актуальны только методы которые есть у А, в методах В он не уверен, поэтому и требует чтобы взяли на себя ответственность и сказали ему кто есть кто )
Grigoryvvv Уровень 28 Expert
13 августа 2026
13.08.2026 / 24 уровень
@itArarat Уровень 32
29 января 2026
помоему Core легче
Матвей Уровень 16
21 февраля 2026
нет, Core легче, лишь потому-что ты уже проходил эти лекции в Java Syntax Pro, а если с нуля, то Core сложнее
Oleg Уровень 5 Expert
30 сентября 2025
Как-то не очень понятно(
Sandra M Уровень 1
8 сентября 2025
Вроде понятно, а вроде и не понятно... надеюсь, задачи порешаю и пойму поняла или нет)))
Exaltyr777 Уровень 25
3 сентября 2025
Напишите определение расширения\сужения типа почётче пожалуйста, а то "мы двигаемся" как-то непонятно. Лучше напишите что конкретно преобразуется.
Anonymous #3554585 Уровень 68
21 августа 2025
С одной стороны вроде бы понятно. Но с другой стороны как это использовать на практике? Если например создать массив animal, то в него можно внести классы dog и cat, а потом перебирать этот массив в цикле. Но какой смысл тогда в этом, если нельзя вызывать методы, описанные в в классах dog и cat?
Anonymous #3585174 Уровень 33
11 августа 2025
Like
ShadyLine Уровень 51
22 июня 2025
Тему довольно таки коротко и сложно раскрыли... не совсем все понятно.
AndreyRjr Уровень 25
16 апреля 2025
В этой теме просто немножко логика в голове отказывается воспринимать понятия... Upcasting - по иерархии наследования идем вверх, а вот по функционалу вниз(в сторону уменьшения функций). Downcasting - по иерархии наследования идем вниз, а по функционалу вверх(в сторону увеличения функций). Для себя так это попробовал уложить в голове. Названия исключительно из-за иерархии, а не функционала.
VadChet Уровень 29
25 мая 2025
Думаю, это можно представить в виде корневой системы: на самом верху Начало - Object, далее от него растут все остальные объекты/классы. Они растут охватывая всё большее пространство (расширяя функционал). Если перерубить на каком-то уровне, то "останутся живы" (доступны для использования) только объекты выше по корню, вплоть до Object.