Скажем, у меня есть классное животное и классы кошка и собака, которые распространяют животное. У кошки, животного и собаки есть метод speak (), который для кошки печатает «мяу», а для собаки - «гав» и для животного «не могу говорить».
Хорошо, наконец, вот мой вопрос. Что именно происходит, если сделать кошку (c
) и запустить Animal a = c;
? Что произойдет, если я бегу a.speak();
? Какой метод speak()
вызывается? Что именно произошло, когда я изменил такие типы? У меня когда-нибудь будет реальная причина использовать это?
Объекты в Java точно знают, какого типа они были созданы; фактически это скрытое поле (которое вы можете получить с помощью метода Object.getClass()
). Более того, все нестатические методы разрешения начинаются с определений методов наиболее специфического класса и переходят к наиболее универсальному классу (Object
); поскольку в Java существует только одно наследование реализации, это простой поиск. Cat
знает, что это подтип Animal
, который является подтипом Object
, а c
знает, что это Cat
независимо от типа переменной.
Когда вы делаете присваивание, компилятор проверяет, является ли известный тип присваиваемого значения типом, присваиваемым или одному из его подтипов . Если это так, назначение работает. Если это не так, вам понадобится явное приведение (которое вставляет правильную проверку типов во время выполнения; приведение не может сломать систему типов Java, они могут просто сделать это уродливым). Это не меняет того факта, что поиск метода все еще выполняется динамически, и объект все еще знает, какой это тип; все, что делает программа, это пренебрежение некоторой информацией.
Если вы знаете C ++, то думайте, что в Java есть только виртуальные методы (и статические методы), и очень просто обрабатывать запросы в vtables, потому что нет проблем с наследованием алмазов или другими злыми случаями.
При работе с реализациями интерфейса это почти то же самое, за исключением того, что выполняется более сложный поиск (т. Е. Сначала просматривается индекс в vtable, а затем продолжается, как и раньше). Однако наличие объекта, реализующего интерфейс, означает, что должен существовать некоторый класс, который полностью реализует интерфейс, и к этому моменту все снова относительно просто. И помните, все действительно сложные вещи делаются во время компиляции; во время выполнения все относительно просто во всех случаях.
Итак, вы воспользуетесь этим? Ну, вы должны (и вам действительно будет очень трудно избежать в реальном коде). Это хороший стиль, чтобы думать о контракте, определяемом интерфейсом или суперклассом, когда подтипы подчиняются контракту, и вызывающая сторона не должна знать о деталях. Библиотеки Java используют это очень интенсивно, особенно потому, что видимость деталей типа контракта и его выполнение могут быть разными. Все, что клиентский код знает, это то, что объект подчиняется договору, что он имеет данный тип.