Я думаю, что для того, чтобы понять это, нужно сначала понять тонкие различия между типами и классами: у объектов есть классы, у ссылок есть типы.
Классы и типы
Я думаю, что пример - хороший способ объяснить, что я здесь имею в виду. Предположим, что существует следующая иерархия классов:
class Mammal {}
class Feline extends Mammal {}
class Lion extends Feline {}
class Tiger extends Feline {}
Благодаря полиморфизму подтипов мы могли объявить несколько ссылок на один и тот же объект.
Tiger tiger = new Tiger(); //!
Feline feline = tiger;
Mammal mammal = feline;
Интересно, что если бы мы спросили у всех этих ссылок название их класса, они все ответили бы одним и тем же ответом: «Тигр».
System.out.println(tiger.getClass().getName()); //yields Tiger
System.out.println(feline.getClass().getName()); //yields Tiger
System.out.println(mammal.getClass().getName()); //yield Tiger
Это означает, что фактический класс объекта фиксирован, это тот класс, который мы использовали для его создания, когда использовали оператор «новый» в нашем коде выше.
С другой стороны, ссылки могут иметь разные типы, полиморфно совместимые с фактическим классом объекта (то есть с тигром в данном случае).
Итак, у объектов есть фиксированный класс, тогда как у ссылок есть совместимые типы.
Это может сбивать с толку, поскольку имена классов - это то же самое, что мы используем для именования типов наших ссылок, но семантически говоря, есть тонкая разница, как мы можем видеть выше.
Возможно, самой запутанной частью является осознание того, что классы также являются объектами и поэтому могут иметь собственные совместимые ссылки. Например:
Class<Tiger> tigerClass = null;
Class<? extends Tiger> someTiger = tigerClass;
Class<? extends Feline> someFeline = tigerClass;
Class<? extends Mammal> someMammal = tigerClass;
В моем коде выше упоминаемый объект является объектом класса (который я оставил на время равным нулю), и эти ссылки, используемые здесь, имеют разные типы для достижения этого гипотетического объекта.
Итак, как видите, слово «класс» здесь используется для обозначения «типа ссылки», указывающего на реальный объект класса, тип которого совместим с любой из этих ссылок.
В моем примере выше мне не удалось определить такой объект класса, поскольку я инициализировал исходную переменную нулевым значением. Это сделано намеренно, и через мгновение мы поймем, почему.
О вызове getClass
и getSubclass
в ссылках
Следуя примеру @Jatin, который рассматривает метод getClass
, давайте теперь рассмотрим следующий фрагмент полиморфного кода:
Mammal animal = new Tiger();
Теперь мы знаем, что независимо от того, что наша ссылка animal
имеет тип Mammal
, фактическим классом объекта является и всегда будет Tiger
(т. Е. Класс).
Что мы получим, если сделаем это?
? type = mammal.getClass()
Должен type
быть Class<Mammal>
или Class<Tiger>
?
Проблема в том, что когда компилятор видит ссылку типа Mammal
, он не может сказать, на какой фактический класс объекта указывает эта ссылка. Это можно было определить только во время выполнения, верно ?. На самом деле это может быть млекопитающее, но с таким же успехом может быть любой из его подклассов, например, тигр, верно?
Итак, когда мы запрашиваем его класс, мы не получаем Class<Mammal>
, потому что компилятор не может быть уверен. Вместо этого мы получаем Class<? extends Mammal>
, и это имеет гораздо больший смысл, потому что, в конце концов, компилятор знает, что, основываясь на правилах полиморфизма подтипов, данная ссылка может указывать на Mammal или любой из его подтипов.
На этом этапе вы, вероятно, можете увидеть тонкие нюансы использования слова «класс». Казалось бы, то, что мы фактически получаем от getClass()
метода, - это некая ссылка на тип, которую мы используем для указания на фактический класс исходного объекта, как мы уже объясняли ранее.
То же самое можно сказать и о методе asSubclass
. Например:
Mammal tiger = new Tiger();
Class<? extends Mammal> tigerAsMammal = tiger.getClass();
Class<? extends Feline> tigerAsFeline = tigerAsMammal.asSubclass(Feline.class);
Когда мы вызываем asSubclass
, мы получаем ссылку на фактический тип класса, на который указывает наша ссылка, но компилятор больше не может быть уверен в том, какой должна быть эта фактическая природа, и поэтому вы получаете более слабую ссылку, такую как Class<? extends Feline>
. Это максимум, что компилятор может предположить об исходной природе объекта, и поэтому это все, что мы получаем.
А как насчет new Tiger (). gerClass ()?
Мы могли бы ожидать, что единственный способ получить Class<Tiger>
(без wirldcards) должен быть доступ к исходному объекту, верно? Нравиться:
Class<Tiger> tigerClass = new Tiger().getClass()
Забавно, однако, что мы всегда достигаем объекта тигра через ссылочный тип, не так ли? В Java у нас никогда не бывает прямого доступа к объектам. Поскольку мы всегда достигаем объекта через их ссылки, компилятор не может делать предположений о фактическом типе возвращаемой ссылки.
Вот почему даже этот код приведет к Class<? extend Tiger>
Class<? extends Tiger> tigerClass = new Tiger().getClass();
То есть компилятор не дает никаких гарантий относительно того, что может здесь вернуть оператор new
. Во всем, что имеет значение, он может вернуть объект, совместимый с Tiger, но не обязательно тот, классом которого является сам Tiger.
Это станет яснее, если вы измените оператор new
для заводского метода.
TigerFactory factory = new TigerFactory();
Class<? extends Tiger> tigerClass = tigerFactory.newTiger().getClass();
И наша фабрика Tiger:
class TigerFactory {
public Tiger newTiger(){
return new Tiger(){ } //notice this is an anonymous class
}
}
Надеюсь, это как-то способствует обсуждению.
person
Edwin Dalorzo
schedule
19.02.2015