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.

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

Комментарии (422)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Oleg Уровень 24 Expert
30 сентября 2025
Как-то не очень понятно(
Sandra M Уровень 20
8 сентября 2025
Вроде понятно, а вроде и не понятно... надеюсь, задачи порешаю и пойму поняла или нет)))
Exaltyr777 Уровень 25
3 сентября 2025
Напишите определение расширения\сужения типа почётче пожалуйста, а то "мы двигаемся" как-то непонятно. Лучше напишите что конкретно преобразуется.
Anonymous #3554585 Уровень 66
21 августа 2025
С одной стороны вроде бы понятно. Но с другой стороны как это использовать на практике? Если например создать массив animal, то в него можно внести классы dog и cat, а потом перебирать этот массив в цикле. Но какой смысл тогда в этом, если нельзя вызывать методы, описанные в в классах dog и cat?
Anonymous #3585174 Уровень 33
11 августа 2025
Like
ShadyLine Уровень 30
22 июня 2025
Тему довольно таки коротко и сложно раскрыли... не совсем все понятно.
AndreyRjr Уровень 25
16 апреля 2025
В этой теме просто немножко логика в голове отказывается воспринимать понятия... Upcasting - по иерархии наследования идем вверх, а вот по функционалу вниз(в сторону уменьшения функций). Downcasting - по иерархии наследования идем вниз, а по функционалу вверх(в сторону увеличения функций). Для себя так это попробовал уложить в голове. Названия исключительно из-за иерархии, а не функционала.
VadChet Уровень 29
25 мая 2025
Думаю, это можно представить в виде корневой системы: на самом верху Начало - Object, далее от него растут все остальные объекты/классы. Они растут охватывая всё большее пространство (расширяя функционал). Если перерубить на каком-то уровне, то "останутся живы" (доступны для использования) только объекты выше по корню, вплоть до Object.
neptun020202 Уровень 32
7 марта 2025
Немного из общения с ИИ... Может кому-то что-то прояснит В Java приведение типов (type casting) для ссылочных типов связано с иерархией наследования и интерфейсами. Приведение может быть двух видов: расширение (upcasting) и сужение (downcasting). 1. Расширение (Upcasting) Расширение происходит автоматически, когда объект подкласса присваивается переменной суперкласса или интерфейса. Это безопасно, так как подкласс всегда содержит все методы и поля суперкласса. Пример:

class Animal {
    void makeSound() {
        System.out.println("Animal sound");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Woof");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Animal animal = dog; // Upcasting: Dog -> Animal
        animal.makeSound(); // Выведет "Woof"
    }
}
Здесь объект Dog автоматически приводится к типу Animal. Это безопасно, так как Dog является подклассом Animal.
neptun020202 Уровень 32
7 марта 2025
2. Сужение (Downcasting) Сужение требует явного приведения типов и может быть опасным, так как оно предполагает, что объект, который был расширен до суперкласса, может быть снова сужен до подкласса. Если объект на самом деле не является экземпляром этого подкласса, будет выброшено исключение ClassCastException. Пример:

class Animal {
    void makeSound() {
        System.out.println("Animal sound");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Woof");
    }

    void fetch() {
        System.out.println("Fetching the ball");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // Upcasting: Dog -> Animal
        Dog dog = (Dog) animal; // Downcasting: Animal -> Dog
        dog.makeSound(); // Выведет "Woof"
        dog.fetch(); // Выведет "Fetching the ball"
    }
}
В этом примере объект Animal явно приводится к типу Dog. Это безопасно, так как на самом деле объект является экземпляром Dog.
neptun020202 Уровень 32
7 марта 2025
Опасность сужения (Downcasting) Если объект не является экземпляром целевого класса, то при попытке сужения будет выброшено исключение ClassCastException. Пример:

class Animal {
    void makeSound() {
        System.out.println("Animal sound");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Woof");
    }
}

class Cat extends Animal {
    void makeSound() {
        System.out.println("Meow");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Cat(); // Upcasting: Cat -> Animal
        Dog dog = (Dog) animal; // Опасное сужение: Animal -> Dog
        // Будет выброшено ClassCastException, так как animal на самом деле Cat
    }
}
neptun020202 Уровень 32
7 марта 2025
Проверка перед сужением (instanceof) Чтобы избежать ClassCastException, перед сужением рекомендуется использовать оператор instanceof для проверки типа объекта. Пример:

public class Main {
    public static void main(String[] args) {
        Animal animal = new Cat();

        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.makeSound();
        } else {
            System.out.println("animal is not a Dog");
        }
    }
}
Итог Upcasting (расширение) безопасно и происходит автоматически. Downcasting (сужение) требует явного приведения и может быть опасным, если объект не является экземпляром целевого класса. Используйте instanceof для проверки типа перед выполнением сужения.
Faraway_is_close Уровень 24
3 марта 2025
Ничё не понял
Anonymous #3466415 Уровень 25
7 февраля 2025
Все просто, когда ты родился ты ребенок у тебя есть метод сосать соску, лежать в кровати, смотреть телик. Потом тебе стукнуло 16 - по сути ты еще ребенок, но тебе дали паспорт и сказали что ты взрослый. И ты уже не можешь сосать соску, но способность - лежать в кровати и смотреть телик, тебя еще никто не лишил. 🤪