Доброго времени суток, джаварашовец. Начали мне поступать вопросы о приведение ссылочных типов в Java. Что бы каждый раз не пересказывать одно и то же, решил написать маленькую статью.
Сначала разберем, что же такое приведение типов
Приведение типов (преобразование типов) — преобразование значения переменной одного типа в значение другого типа. Давайте посмотрим на примере, что это такое и с чем его едят. У нас есть некоторая иерархия классов (см. Рисунок 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
.
Это очень хорошо, потому что, если бы мы могли вызвать все методы, то зачем нам нужен волк который спит на клавиатуре, или йорик который ворует куриц?
Спасибо за внимание. Надеюсь, эта статья для Вас будет полезна. Критика и комментарии приветствуются)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Мухи — переменные примитивного типа. У них нет ссылок, они просто занимают в памяти определённое количество байт. Тип byte занимает один байт ( 8 бит). Тип short требует два байта (16 бит). Теперь, если Вы переливаете из 8-и битного ведра в 16-и битное, то все биты перельются без потерь.
В данном случае приведение типов выполняется компилятором самостоятельно (неявно), т.е. нет необходимости писать short s = (short) b Мы преобразуем (расширяем) менее ёмкий по памяти тип до более ёмкого. Преобразования с расширением типов выполняются компилятором неявно.
В коде ниже происходит сужение типа соответственно. Часть битов из 16-и битного ведра даром пропадают при переливании в 8-и битное ведро.
Происходит сужение типа, которое мы должны обозначить явно.
"-Пол литра в дребезги? Да?!
-Да!
-Да я тебя!!!..."
Теперь разберёмся с котлетами- переменными ссылочного типа.
Переменная указывает (хранит адрес в памяти) на экземпляр объекта (конкретные данные занимающие часть памяти в зависимости от архитектуры объекта). Ссылочная переменная должна иметь тип, т.к. Java — строго типизированный язык. В самом простом варианте тип ссылочной переменной при присвоении должен соответствовать классу на основе которого создаётся экземпляр объекта:
В свете наследования классов друг от друга и полиморфизма всё усложняется. При приведении переменной типа потомок к переменной типа родитель мы теряем те поля, которые были определены только в потомке, т.е. обращаясь к объекту потомок через ссылочную переменную типа родитель, мы увидим только те переменные и методы, которые были унаследованы