Некоторые сомнения относительно полиморфизма Java применительно к случаю Calendar и GregoriaCalendar

У меня есть следующее сомнение.

В моем коде у меня есть:

Calendar today = Calendar.getInstance();

где сегодняшняя переменная является экземпляром Calendar, поэтому я не могу использовать для нее такие методы, как isLeapYear().

Делая таким образом, я могу выполнить этот метод:

GregorianCalendar today = (GregorianCalendar) Calendar.getInstance();
int currentYear = today.get(Calendar.YEAR);

boolean bisestile = today.isLeapYear(currentYear);

Я сомневаюсь: именно почему? Я привожу тот же результат, возвращенный Calendar.getInstance(), в GregorianCalendar.

Читать здесь: http://tutorials.jenkov.com/java-date-time/java-util-calendar.html

мне кажется, я понимаю, что класс java.util.Calendar является абстрактным, поэтому я не могу создать его экземпляр, поэтому я думаю, что Calendar.getInstance() автоматически возвращает объект GregorianCalendar, который определил предыдущий метод isLeapYear().

Но я не могу использовать его, если объект определен как простой Calendar, а не как GregorianCalendar.

Я знаю о полиморфизме, но как именно работает в этом конкретном случае?

Я думаю, что размещение ссылки на объект GregorianCalendar (возвращенный функцией Calendar.getInstance(), верно?) в Calendar (я может это сделать, потому что Calendar — это супертип) я могу получить доступ только к подмножеству методов, определенных для этого абстрактного класса, а не ко всем методам, определенным для конкретного типа.

Правильно ли это рассуждение или я что-то упускаю?


person AndreaNobili    schedule 25.08.2016    source источник


Ответы (3)


Это полиморфизм. Calendar предоставляет абстрактную структуру и подклассы, такие как GregorianCalendar предоставляет реализации. В других контекстах имейте в виду, что Calendar.getInstance() может вернуть (например) китайский или еврейский календарь в зависимости от местоположения и настройки системы.

Если вам действительно нужно GregorianCalendar явно , объявите переменную как таковую.

GregorianCalendar cal = new GregorianCalendar();
person ControlAltDel    schedule 25.08.2016

Правильный ответ от ControlAltDel.

Одним из преимуществ объектно-ориентированного программирования является предотвращение хрупкого программного обеспечения, которое ломается после внесения некоторых изменений. Один из способов добиться этого — определить интерфейс с одной или несколькими реализациями. Когда другой код ссылается на интерфейс, мы можем отключить реализацию, не нарушая вызывающий код.

Таким образом, общее правило заключается в том, чтобы использовать самый высокий уровень интерфейса, соответствующий вашим потребностям.

Пример: коллекции

Например, в коллекциях Java используйте Collection, где он соответствует вашим потребностям.

Collection<String> col = new ArrayList<>() ;

Будьте более конкретными, только если ваш код требует дополнительных методов, предлагаемых более конкретным интерфейсом. Здесь мы используем List, интерфейс Collection, который обещает дополнительные методы.

List<String> list = new ArrayList<>() ;

Используйте ArrayList как ArrayList только в случае крайней необходимости, только если этот класс предлагает определенные методы, которые вам нужны, но не включены в его более общие интерфейсы.

ArrayList<String> list = new ArrayList<>() ;

Позже мы можем решить заменить этот ArrayList с LinkedList. Это не сломает код, который ожидал List или Collection, но сломает любой вызывающий код, который ожидал именно ArrayList.

List<String> list = new LinkedList<>();

Calendar и GregorianCalendar

Точно так же вы должны использовать Calendar везде, где это возможно. , а не GregorianCalendar.

Calendar cal = new GregorianCalendar() ;

Но если коду абсолютно необходимы специальные функции GregorianCalendar, тогда отслеживайте объект как GregorianCalendar, как показано в Ответе ControlAltDel.

GregorianCalendar cal = new GregorianCalendar() ;

Вы можете смешать это, используя более конкретный тип внутри и более общий тип снаружи. Если вам нужны только эти специальные функции GregorianCalendar внутри вашего собственного класса, но вы хотите предоставить доступ к календарю другим классам, вы можете объявить объект календаря как GregorianCalendar в качестве закрытого члена вашего класса, возвращая Calendar из метода получения.

…
private GregorianCalendar gregCal = new GregorianCalendar() ;
…
public Calendar getCalendar() {
    return this.gregCal ;  // Automatically upcast. Appears to any calling code to be a `Calendar`.
}

P.S. java.util.Calendar и .GregorianCalendar являются частью проблемных старых устаревших классов даты и времени, которые теперь вытеснены классами java.time, встроенными в Java 8 и более поздние версии. Избегайте этих старых классов.

person Basil Bourque    schedule 25.08.2016
comment
Общее правило состоит в том, чтобы использовать самый высокий уровень интерфейса, соответствующий вашим потребностям. - Это может сломаться, см. также мой ответ и данную ссылку на официальный javadoc ChronoLocalDate (команда JSR-310 сделала противоположное заявление). Проблема заключается в интерпретации результатов общих методов интерфейса. - person Meno Hochschild; 29.08.2016

Если вы находитесь в Таиланде, ваш код

GregorianCalendar today = (GregorianCalendar) Calendar.getInstance();

вероятно, потерпит неудачу, выбросив ClassCastException, потому что Calendar.getInstance() может привести к тайско-буддийскому календарю по умолчанию в этой стране. Этот вариант использования указывает на серьезное предупреждение использовать Calendar.getInstance(), если вы действительно находитесь в Таиланде. И тогда вы не сможете делать свои предположения на основе григорианского языка, когда собираетесь интерпретировать такие выражения, как calendar.get(Calendar.YEAR) (возможно, давая значения года, такие как 2543).

Вывод. При обработке даты и времени гораздо лучше НЕ использовать общие интерфейсы, а быть как можно более конкретными. Я предполагаю, что вас интересует только григорианский календарь, поэтому используйте:

GregorianCalendar today = new GregorianCalendar();

Кстати, основные разработчики нового пакета java.time в Java-8 также приняли этот представить как можно конкретнее:

Большинство приложений должны объявлять сигнатуры методов, поля и переменные как LocalDate, а не этот интерфейс.

На практике это означает: вам следует избегать таких типов, как TemporalAccessor, ChronoLocalDate и т. д., или в старом мире: избегать таких типов, как java.util.Calendar, в пользу конкретных типов, таких как GregorianCalendar. Полиморфизм — не очень хорошая идея в мире даты и времени. Конкретные типы слишком разные (иногда очень тонкие).

person Meno Hochschild    schedule 29.08.2016