До Java 8
, в стандартной библиотеке существовал и существует класс для задания точки на временной оси - и этот класс
java.util.Date
. Рассматривать мы его будем вместе с java.util.Calendar
- классом использующимся для операций с временем.
java.util.Date
появился в Java 1.0
и так получилось, что был крайне неудачно спроектирован.
Однако, сейчас его использование считается плохой практикой.
Давайте бегло посмотрим - что из себя представляет данный класс, а после - опишем уже почему использовать его - плохой тон.
Итак, java.util.Date
.
Основное поле класса - это:
private transient long fastTime;
В данном поле хранится количество количество миллисекунд с 1 января 1970 года - Unix-time
в миллисекундах.
Можно считать, что java.util.Date
- это обертка над long
.
Из этого сразу следует - что точность определения точки на временной оси составляет 1 миллисекунду.
Т.е java.util.Date
- по сути представляет отрезок на временной оси, отрезок длиной 1 миллисекунда.
В целом - точности java.util.Date
хватает и я лично еще не сталкивался с тем, что нужны библиотеки точнее.
Именно поэтому, мы можем представлять себе объект java.util.Date
как точку на временной оси.
Когда мы узнали что вообще из себя примерно представляет внутри java.util.Date
- можно задать вопрос - хватит ли нам long
-а для хранения количества миллисекунд вообще?
Вспомним, что максимальное значение long
в Java
- это
Long.MAX_VALUE = 9223372036854775807 // так как в Java 64-битное представление
Поэтому вместимости данного поля хватит на века вперед. Так что не стоит переживать по этому поводу!
Продолжим знакомство с java.util.Date
и взглянем на обратную сторону медали - на его минусы.
Давайте попробуем дать объяснение тому факту, что от java.util.Date
сейчас все стараются уходить?
Date
изменяемый, что порождает много ошибок - это потоконебезопасно.- Отсутствует поддержка часовых поясов.
- Имя класса абсолютно не отражает его сути.
- Из-за странного проектирования - сложное использование класса.
С первыми двумя пунктами, я думаю, интуитивно понятно что не так. Разберем последние два.
Так как java.util.Date
, как уже было сказано выше, по сути представляет из себя точку на временной оси - это не может быть датой и т.д. Это очень даже некоторый Instant
- некоторый момент, мгновение.
Но тем не менее класс носит гордое название Date, чем вводит в заблуждение.
Ну а сложное использование класса лучше продемонстрировать на практике:
Date date = new Date(2016, 12, 13, 14, 49);
В данном коде сразу два подводных камня.
- На месте, где стоит
2016
- конструктор классаjava.util.Date
ждет отступ от 1900 года. - Нумерация месяцев тут начинается с 0, поэтому месяца 12 нет.
Добавим к этому то, что в Java 8
все методы данного класса помечены как @Deprecated
.
Это пробелы именно в проектировании класса - подобное API
запутанно, не последовательно и легко может привести к ошибкам.
Добавим к этому еще и мутабельность - получим крайне несбалансированный класс, в использовании которого легко ошибиться, эти ошибки не будут проверены на этапе компиляции - трудно уловимы, вдобавок ко всему еще и крайне критичны.
В связи со всем вышеописанным - пользоваться данным классом мягко говоря не удобно, поэтому в Java 8
ввели новое Java 8 Time API.
Для связи старого и нового API в java.util.Date
существуют методы-бриджи.
Это Date from(Instant instant)
и Instant toInstant()
, которые были добавленны начиная с Java 8
.
Необходимо еще отметить вот что - для операций со временем в Java
существуют классы: java.util.Calendar
, java.util.TimeZone
.
Для сравнения двух экземпляров java.util.Date
существует три способа:
-
getTime()
Чтобы получить количество миллисекунд, прошедших с момента полуночи
1 января 1970
, для обоих объектов, а затем сравнить эти два значения. -
Методы
before()
,after()
иequals()
.Поскольку 12 число месяца раньше 18 числа, например:
new Date(99, 2, 12).before(new Date (99, 2, 18))
возвращает значение
true
. -
compareTo()
, который определяется сопоставимым интерфейсом и реализуется по дате.
Если мы собираемся взаимодействовать с временем не только как с точками на временной оси, то нам необходимо использовать дополнительные классы.
Классы java.util.Calendar
и java.util.TimeZone
описывают сущности календаря и часового пояса в Java
соответственно.
Я думаю, интуитивно понятно для чего они нужны, но считаю, что необходимо также сделать некоторое overview на них.
Итак.
К сожалению, странный подход к проектированию коснулся и этого класса:
- Месяцы идут с 0.
- Дни - начинаются с 1.
- При установке полной даты количество миллисекунд не сбрасывается(необходим вызов специального метода), а остается равным количеству миллисекунд с предыдущего момента или текущего времени - если не было изменения календаря.
Третий пункат означает вот что:
TimeZone tz = TimeZone.getTimeZone("Europe/Moscow");
Calendar calendar = Calendar.getInstance(tz);
calendar.set(2018, Calendar.MARCH, 4, 9, 2, 11);
System.out.println(calendar.getTimeInMillis());
calendar.set(Calendar.MILLISECOND, 0);
System.out.println(calendar.getTimeInMillis());
И получим два разных значения - отличающиеся именно в трех последних числах - как раз из-за того, что описано в пункте 3:
1520143331925
1520143331000
Теперь должен возникнуть вопрос - откуда java.util.Date
берет значения для инициализации? Где источник?
Источником - понятное дело - выступает система.
В Java
есть следующие методы для получения времени:
java.lang.System.currentTimeMills
Из документации:
* @return the difference, measured in milliseconds, between * the current time and midnight, January 1, 1970 UTC.
Что значит, что он возвращает количество миллисекунд прошедших с полночи 1 января 1970 по UTC
.
Гарантий про многопоточность данный метод не дает - в результате перевода системных часов могут быть погрешности. К счастью, подобных переводов часто не делают - я не сталкивался ни разу.
При создании экземпляров java.util.Date
и java.util.Calendar
используется именно этот метод.
Ключевое слово
native
означает, что метод реализован в платформенно-зависимом коде, чаще всего наC/C++
, и скомпонован в виде динамической библиотеки.Эта реализация зависит от
JVM
.
Возможно, вас сейчас это напугало, но на самом деле достаточно просто понимать, что
native
означает лишь то, что вызываемый код, реализован не наJava
.
java.lang.System.nanoTime
Из документации:
* @return the current value of the running Java Virtual Machine's * high-resolution time source, in nanoseconds
Что значит, что он возвращает некоторое абстрактное количество наносекундных 'тиков'. Реализация зависит от железа, типа и версии ОС.
Исходя из описаний методов можно заключить следующее - для измерения точнойдлительности операций допустимо использовать именно java.lang.System.nanoTime
.
Более-менее современный код именно так и работает - для примера можно посмотреть реализации интерфейса java.util.concurrent.ExecutorService
.
В более старом коде же до сих пор иногда можно встретить использование java.lang.System.currentTimeMills
для подобных вещей - для примера можно посмотреть на java.lang.Thread
.
Совершенно ясно, что использовать конструкции вида:
event.setGeneratedDate(new Date());
event.setProcessingDate(new Date());
Не должны быть в вашем коде - они должны быть представлены в виде:
Date now = new Date();
event.setGeneratedDate(now);
event.setProcessingDate(now);
Так как раньше java.util.Date
был единственным классом для работы с временем в Java
, если не учитывать сторонние библиотеки, то он является родительским классом для сразу нескольких классов, связанных с представлением хранения времени в БД.
java.sql.Timestamp
представлениеTIMESTAMP
в БД.java.sql.Date
представлениеDATE
в БД.java.sql.Time
представлениеTIME
в БД.
Подробнее об этом в Хранение даты в БД
Также, важно рассказать еще и то, как работать с датой, если она представлена как строка - для этой цели существуют специальные классы форматтеры, для java.util.Date
такой форматтер - это SimpleDateFormat
Данный класс до сих пор используется в некоторых проектах в качестве помощника для работы с временем. Однако из-за явных минусов, описанных выше, лично я не рекомендовал бы использовать данный API
сейчас.
Если вы пишите на Java
до 8-й версии, то лучше использовать стороннюю библиотеку joda-time.
При использовании Java 8+
- вполне подойдет использование Java 8 Time API.
С недавних пор даже Hibernate
умеет работать с Java 8 Time API
, так что проблем быть не должно.