JavaRush/Java блог/Архив info.javarush/Преобразование ссылочных типов в Java
Sant9Iga
41 уровень

Преобразование ссылочных типов в Java

Статья из группы Архив info.javarush
участников
Доброго времени суток, джаварашовец. Начали мне поступать вопросы о приведение ссылочных типов в Java. Что бы каждый раз не пересказывать одно и то же, решил написать маленькую статью.

Сначала разберем, что же такое приведение типов

Приведение типов (преобразование типов) — преобразование значения переменной одного типа в значение другого типа. Давайте посмотрим на примере, что это такое и с чем его едят. У нас есть некоторая иерархия классов (см. Рисунок 1). Тут видно все классы иерархии, кто кого наследует и методы каждого класса. Рисунок 1Есть расширяющее и сужающее приведение. Мы видим, что класс Cat является наследником класса Pet. Pet, в свою очередь, наследник класса Animal. Когда мы напишем:
Animal animalCat = new Cat();
Animal animalDog = new YorkshireTerrier();
Это расширяющее приведение (или неявное). Мы расширили ссылки animalCat и animalDog. Они ссылаются на объекты Cat и Dog. При таком приведении мы не можем через ссылку animalCat/animalDog вызвать методы, которые есть в Cat/Dog, но которых нету в Animal. Сужающее приведение(или явное) происходит в обратную сторону:
Animal animalCat = new Cat();
Animal animalDog = new YorkshireTerrier();
Cat cat =(Cat)animalCat;
YorkshireTerrier dog = (YorkshireTerrier) animalDog;
Мы явно указали к какому типу хотим привести данный объект. BUT, BE CAREFUL!!! Если Вы сделаете вот так:
Animal animalCat = new Cat();
YorkshireTerrier dog = (YorkshireTerrier) animalCat;
компилятор пропустит этот код. А вот RunTime выкинет вам:
Exception in thread "main" java.lang.ClassCastException: Animals.Cat cannot be cast to Animals.YorkshireTerrier
RunTime видит, что Cat и YorkshireTerrier два разных класса. Что бы избежать ClassCastException при сужающем преобразовании, используйте instanceof.
Animal animalCat = new Cat();
if (animalCat instanceof YorkshireTerrier)
{
    YorkshireTerrier dog = (YorkshireTerrier) animalCat;
}
Если animalCat является YorkshireTerrier, тогда произойдет присвоение, если нет — ничего не произойдет.

А теперь зачем же это нужно, если мы теряем методы и можем получить такие ошибки

Рассмотрим код который я сделал по диаграмме с Рис. 1. Класс Animal
public abstract class Animal
{
    String name;
    int age;
    String nameOfClass = getClass().getSimpleName();
    public void eat(){
        System.out.println(nameOfClass + ": Omnomnom");
    }
    public void sleep(){
        System.out.println(nameOfClass + ": Z-z-z-z");
    }
}
Класс WildAnimal который наследуется от Animal
public abstract class WildAnimal extends Animal
{
    public void steelChicken()
    {
        System.out.println(nameOfClass+": Muhaha,I stole a chicken!");
    }
}
Класс Pet который наследуется от Animal
public abstract class Pet extends Animal
{
    public void peeInTray(){
        System.out.println(nameOfClass + ": Master, I peed");
    }
}
Класс Fox который наследуется от WildAnimal
public class Fox extends WildAnimal
{
    public void eatColobok(){
        System.out.println(nameOfClass + ": I will eat you, Colobok");
    }
}
Класс Wolf который наследуется от WildAnimal
public class Wolf extends WildAnimal
{
    public void hawlAtTheMoon(){
        System.out.println(nameOfClass + ": Ouuuuu!!!Ouuuu!!!");
    }
}
Класс Cat который наследуется от Pet
public class Cat extends Pet
{
    public void sleepOnKeyboard(){
        System.out.println(nameOfClass + ": Master, stop working!!I wanna sleep on your keyboard");
    }
}
Класс YorkshireTerrier который наследуется от Pet
public class YorkshireTerrier extends Pet
{
    public void bark(){
        System.out.println(nameOfClass + ": Meow!!! Meow!!!");
    }
}
Представьте себе ситуацию. Нам нужно собрать всех животных в один список, накормить их и потом уложить спать. Это легко сделать, если мы создадим ArrayList животных (Animal). И потом у каждого животного вызовем соответствующие методы:
public class ZOO
{
    public static void main(String[] args)
    {
        List<Animal> allAnimals = new ArrayList<>();
        allAnimals.add(new Cat());
        allAnimals.add(new Wolf());
        allAnimals.add(new Fox());
        allAnimals.add(new YorkshireTerrier());
        for (Animal animal : allAnimals)
        {
            animal.eat();
            animal.sleep();
        }
    }
}
Я не могу у animal вызвать метод bark() или sleepOnKeyboard(). Потому что в листе allAnimals хранятся кот, волк, йорик и лиса, но они приведены к Animal. И у них есть только те методы, которые есть в Animal. Это очень хорошо, потому что, если бы мы могли вызвать все методы, то зачем нам нужен волк который спит на клавиатуре, или йорик который ворует куриц? Спасибо за внимание. Надеюсь, эта статья для Вас будет полезна. Критика и комментарии приветствуются)
Комментарии (38)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
13 сентября 2021, 04:42
Большое спасибо за статью!
Anonymous #2135740
Уровень 35
31 октября 2019, 12:23
Йорик: "Meow!!! Meow!!" 😊
Nordis
Уровень 28
Expert
25 августа 2019, 18:49
Пока читал кто от кого там наследуется , забыл кто там был первый и второй . Но потом посмотрел на рис 1 , и всё понял . Сделано по рисунку .
10 октября 2018, 18:52
Как можно понять такое предложение? Переведите на человеческий русский пожалуйста. "При таком приведении мы не можем через ссылку animalCat/animalDog вызвать методы, которые есть в Cat/Dog, но которых нету в Animal."
10 октября 2018, 18:56
Сам переведу: "При таком приведении через ссылку animalCat/animalDog мы можем вызвать методы родительского класса, и не можем вызвать методы дочернего класса".
Ihor Antonov Backend Developer в wizardsdev
7 июня 2022, 11:23
Там "но" с "и" перепутано.
RaccoonLady
Уровень 13
30 июля 2018, 10:31
List allAnimals = new ArrayList<>(); на эту строку компилятор ругается Не указан тип объектов, которые будут храниться в листе Должно быть List<Animal> allAnimals = new ArrayList<>();
Alex Fedoniuk
Уровень 15
12 июня 2018, 09:39
Код нечитабельный. Так не делается.
REDAS
Уровень 21
17 августа 2017, 09:04
О каком классе Dog идёт речь? Или ты считаешь Dog и YorkshireTerrier это одно и тоже? В программировании даже одна точка важна! Автор аутист, исправь или удали статью, с самого начала читать бессмысленно.
Viktor Vasiuk
Уровень 19
22 июля 2018, 17:07
Аутист тут только один, думаю, догадаешься кто
Алёна С.
Уровень 23
6 сентября 2019, 14:30
Согласен, только аутисты могут оскорблять чужой труд. Есть сильные порывы, напиши статью и в комментариях здесь дай линк, со словами: "Читайте мою статью, она лучше".
Neyron
Уровень 40
16 сентября 2016, 23:11
Доброго времени суток! Давайте отделим мух от котлет и разберём их по отдельности.
Мухи — переменные примитивного типа. У них нет ссылок, они просто занимают в памяти определённое количество байт. Тип byte занимает один байт ( 8 бит). Тип short требует два байта (16 бит). Теперь, если Вы переливаете из 8-и битного ведра в 16-и битное, то все биты перельются без потерь.
<code>
byte b = 127;
short s = b;
</code>
В данном случае приведение типов выполняется компилятором самостоятельно (неявно), т.е. нет необходимости писать short s = (short) b Мы преобразуем (расширяем) менее ёмкий по памяти тип до более ёмкого. Преобразования с расширением типов выполняются компилятором неявно.
В коде ниже происходит сужение типа соответственно. Часть битов из 16-и битного ведра даром пропадают при переливании в 8-и битное ведро.
<code>
short s = 2016;
byte b = (byte) s;
</code>
Происходит сужение типа, которое мы должны обозначить явно.
"-Пол литра в дребезги? Да?!
-Да!
-Да я тебя!!!..."
Теперь разберёмся с котлетами- переменными ссылочного типа.
Переменная указывает (хранит адрес в памяти) на экземпляр объекта (конкретные данные занимающие часть памяти в зависимости от архитектуры объекта). Ссылочная переменная должна иметь тип, т.к. Java — строго типизированный язык. В самом простом варианте тип ссылочной переменной при присвоении должен соответствовать классу на основе которого создаётся экземпляр объекта:
<code>
A instance = new A();
</code>
В свете наследования классов друг от друга и полиморфизма всё усложняется. При приведении переменной типа потомок к переменной типа родитель мы теряем те поля, которые были определены только в потомке, т.е. обращаясь к объекту потомок через ссылочную переменную типа родитель, мы увидим только те переменные и методы, которые были унаследованы
my2636
Уровень 1
7 ноября 2023, 19:32
(включая переопределенные в потомках)
toMy
Уровень 26
12 апреля 2016, 23:47
Огромное спасибо за статью, все доступно и приятно читать. В лекции JR все наоборот написано, что меня и привело к вам)
noxior
Уровень 30
27 февраля 2016, 02:12
ребята, все это абстракция, сужение, расширение, неважно, главное понять кто чьи методы наследует… но по документации: движение по дереву наследования вверх — расширение типа, вниз — сужение. Вот такая вот петрушка)