Доброго времени суток, джаварашовец. Начали мне поступать вопросы о приведение ссылочных типов в 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. Это очень хорошо, потому что, если бы мы могли вызвать все методы, то зачем нам нужен волк который спит на клавиатуре, или йорик который ворует куриц? Спасибо за внимание. Надеюсь, эта статья для Вас будет полезна. Критика и комментарии приветствуются)