Java: Dlaczego konstruktor Data jest przestarzały i czego mam używać zamiast niego?

211

Pochodzę ze świata C #, więc jeszcze nie mam doświadczenia z Javą. Właśnie Eclipse powiedział mi, że Datejest przestarzałe:

Person p = new Person();
p.setDateOfBirth(new Date(1985, 1, 1));

Czemu? A co (szczególnie w przypadkach takich jak powyżej) powinno zostać użyte zamiast tego?

Svish
źródło
37
Mam podobną krzywą uczenia się, przechodzę także z C # na Javę. Inną rzeczą, która mnie ugryzła, jest to, że miesiąc to system oparty na 0 (od 0 do 11, gdzie styczeń = 0 i grudzień = 11), ale dni miesiąca są oparte na 1 (od 1 do 31). Heads up na ten!
Paul Sasik,
2
@Paul Sasik, tak, ale istnieje na przykład stała Calendar.JANUARY i jedna na każdy miesiąc
Diogo
5
@PaulSasik lol. Tak, głupia Java. Musiałem przejść z C # na Javę i OMG, ból i nędzę.
cbmeeks
8
Snarky uwagi „lol” na temat Javy od osób z C # rozśmieszyły mnie, ponieważ .Net ma swoją przyzwoitą bibliotekę daty i godziny ( Noda Time ) z portu doskonałej biblioteki Joda-Time Javy .
Basil Bourque,

Odpowiedzi:

97

Konkretny Datekonstruktor jest przestarzały i Calendarnależy go zamiast tego użyć. JavaDocDla Data opisuje których konstruktorzy są przestarzałe i jak je zastąpić użyciem Calendar.

Ruben
źródło
25
Kalendarz wymaga dodatkowego obiektu i jak 8-liniowy kod więcej, aby zrobić to samo, czyli utworzyć obiekt Date z tego, co mogę powiedzieć. Jest to mylące i wydaje się niepotrzebne, gdy potrzebujesz tylko daty, a nie zmiennej dostosowanej do strefy czasowej.
G_V,
5
Do Twojej wiadomości, strasznie kłopotliwe stare klasy daty i czasu, takie jak java.util.Date, java.util.Calendari java.text.SimpleDateFormatsą teraz spuścizną , wyparte przez klasy java.time wbudowane w Javę 8 i nowsze wersje . Zobacz samouczek Oracle .
Basil Bourque
250

java.util.DateKlasa nie jest faktycznie przestarzałe, tak, że konstruktor, wraz z kilkoma innymi konstruktorami / metody są przestarzałe. Zostało przestarzałe, ponieważ tego rodzaju użycie nie działa dobrze w przypadku internacjonalizacji. CalendarKlasa powinna być stosowana zamiast:

Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 1988);
cal.set(Calendar.MONTH, Calendar.JANUARY);
cal.set(Calendar.DAY_OF_MONTH, 1);
Date dateRepresentation = cal.getTime();

Spójrz na datę Javadoc:

http://download.oracle.com/javase/6/docs/api/java/util/Date.html

BuffaloBuffalo
źródło
2
To powinna być najlepsza odpowiedź. Dzięki.
jordaniac89
Mówiąc „nie działa dobrze z internacjonalizacją”, masz na myśli, że w przypadku Date nie można przypisać do niej strefy czasowej? Dzięki
DiveInto
Jaką datą był parsowanie ciągu, więc zamiast tego musimy teraz podciągać ciąg zawierający rok, miesiąc i dzień? Wydaje się, że jest to dodatkowy problem dla czegoś, co w większości przypadków nie wymaga tak złożonej logiki i dodanych do niej metod.
G_V
1
Wystarczy dodać, że weźmie to pod uwagę domyślną strefę czasową. Jeśli chcemy określić inną strefę czasową, możemy użyćCalendar cal = Calendar.getInstance(TimeZone.getTimeZone(<timezone id>));
Vikas Prasad
1
„Zamiast tego należy użyć klasy Calendar” - w Javie 8 i nowszych java.time.*klasy są lepszą opcją ... jeśli zmieniasz kod.
Stephen C
73

tl; dr

LocalDate.of( 1985 , 1 , 1 )

…lub…

LocalDate.of( 1985 , Month.JANUARY , 1 )

Detale

Te java.util.Date, java.util.Calendaroraz java.text.SimpleDateFormatzajęcia były zbyt szybko, gdy rzucili Java zapoczątkowana i ewoluowały. Klasy nie zostały dobrze zaprojektowane ani wdrożone. Podjęto próby ulepszeń, a zatem znalezione przez ciebie środki wycofania. Niestety próby ulepszeń w dużej mierze zawiodły. Powinieneś całkowicie unikać tych klas . Są one wypierane w Javie 8 przez nowe klasy.

Problemy w twoim kodzie

Plik java.util.Date ma datę i część czasu. Zignorowałeś część czasu w swoim kodzie. Zatem klasa Date zajmie początek dnia zgodnie z domyślną strefą czasową maszyny JVM i zastosuje ten czas do obiektu Date. Tak więc wyniki twojego kodu będą się różnić w zależności od tego, na której maszynie działa lub która strefa czasowa jest ustawiona. Prawdopodobnie nie to, czego chcesz.

Jeśli chcesz tylko datę, bez części czasu, na przykład datę urodzenia, możesz nie chcieć używać Dateobiektu. Możesz zapisać tylko ciąg daty w formacie ISO 8601 z YYYY-MM-DD. Lub użyj LocalDateobiektu z Joda-Time (patrz poniżej).

Joda-Time

Pierwsza rzecz do nauczenia się w Javie: Unikaj notorycznie kłopotliwych klas java.util.Date & java.util.Calendar w pakiecie z Javą.

Jak poprawnie zauważono w odpowiedzi użytkownika3277382 , użyj Joda-Time lub nowego pakietu java.time. * W Javie 8.

Przykładowy kod w Joda-Time 2.3

DateTimeZone timeZoneNorway = DateTimeZone.forID( "Europe/Oslo" );
DateTime birthDateTime_InNorway = new DateTime( 1985, 1, 1, 3, 2, 1, timeZoneNorway );

DateTimeZone timeZoneNewYork = DateTimeZone.forID( "America/New_York" );
DateTime birthDateTime_InNewYork = birthDateTime_InNorway.toDateTime( timeZoneNewYork ); 

DateTime birthDateTime_UtcGmt = birthDateTime_InNorway.toDateTime( DateTimeZone.UTC );

LocalDate birthDate = new LocalDate( 1985, 1, 1 );

Zrzut do konsoli…

System.out.println( "birthDateTime_InNorway: " + birthDateTime_InNorway );
System.out.println( "birthDateTime_InNewYork: " + birthDateTime_InNewYork );
System.out.println( "birthDateTime_UtcGmt: " + birthDateTime_UtcGmt );
System.out.println( "birthDate: " + birthDate );

Po uruchomieniu…

birthDateTime_InNorway: 1985-01-01T03:02:01.000+01:00
birthDateTime_InNewYork: 1984-12-31T21:02:01.000-05:00
birthDateTime_UtcGmt: 1985-01-01T02:02:01.000Z
birthDate: 1985-01-01

java.time

W tym przypadku kod dla java.time jest prawie identyczny z kodem Joda-Time .

Otrzymujemy strefę czasową ( ZoneId) i konstruujemy obiekt daty i godziny przypisany do tej strefy czasowej ( ZonedDateTime). Następnie, korzystając ze wzoru Immutable Objects , tworzymy nowe daty i godziny na podstawie tego samego momentu starego obiektu (liczba nanosekund od epoki ), ale przypisujemy inną strefę czasową. Wreszcie otrzymujemy LocalDatestrefę, która nie ma pory dnia ani strefy czasowej, choć zauważ, że strefa czasowa obowiązuje przy określaniu tej daty (nowy dzień zaczyna się wcześniej w Oslo niż na przykład w Nowym Jorku ).

ZoneId zoneId_Norway = ZoneId.of( "Europe/Oslo" );
ZonedDateTime zdt_Norway = ZonedDateTime.of( 1985 , 1 , 1 , 3 , 2 , 1 , 0 , zoneId_Norway );

ZoneId zoneId_NewYork = ZonedId.of( "America/New_York" );
ZonedDateTime zdt_NewYork = zdt_Norway.withZoneSameInstant( zoneId_NewYork );

ZonedDateTime zdt_Utc = zdt_Norway.withZoneSameInstant( ZoneOffset.UTC );  // Or, next line is similar.
Instant instant = zdt_Norway.toInstant();  // Instant is always in UTC.

LocalDate localDate_Norway = zdt_Norway.toLocalDate();

O java.time

Środowisko java.time jest wbudowane w Javę 8 i nowsze wersje . Klasy te kłopotliwe zastąpić stary starszych klas Date-Time, takich jak java.util.Date, Calendar, i SimpleDateFormat.

Projekt Joda-Time , teraz w trybie konserwacji , zaleca migrację do klas java.time .

Aby dowiedzieć się więcej, zobacz samouczek Oracle . I przeszukaj stos przepełnienia dla wielu przykładów i wyjaśnień. Specyfikacja to JSR 310 .

Możesz wymieniać obiekty java.time bezpośrednio ze swoją bazą danych. Użyj sterownika JDBC zgodnego z JDBC 4.2 lub nowszym. Nie potrzeba ciągów, nie ma potrzeby java.sql.*klas.

Gdzie można uzyskać klasy java.time?

Projekt ThreeTen-Extra rozszerza java.time o dodatkowe klasy. Ten projekt jest poligonem doświadczalnym dla ewentualnych przyszłych dodatków do java.time. Można znaleźć kilka przydatnych klas tutaj takie jak Interval, YearWeek, YearQuarter, i więcej .

Basil Bourque
źródło
5
Rekwizyty do rzeczywistego wymienienia głównej przyczyny i zapewnienia zasięgu wszystkich dostępnych alternatyw.
mabi
To jest poprawna odpowiedź i należy unikać klasy Kalendarza. ThreeTen to najlepsza opcja
ant2009
11

Jednym z powodów, dla których konstruktor jest przestarzały, jest to, że znaczenie parametru year nie jest zgodne z oczekiwaniami. Jawadok mówi:

Od wersji 1.1 JDK, zastąpiony przez Calendar.set(year + 1900, month, date).

Zauważ, że pole roku to liczba lat, które upłynęły od 1900tego czasu , więc Twój przykładowy kod najprawdopodobniej nie spełni twoich oczekiwań. I o to chodzi.

Ogólnie rzecz biorąc, Date interfejs API obsługuje tylko nowoczesny kalendarz zachodni, ma specyficznie określone komponenty i zachowuje się niespójnie, jeśli ustawisz pola.

CalendarI GregorianCalendarAPI są lepsze niż Date, a API Joda czasu 3rd-Party na ogół uważany za najlepszy. W Javie 8 wprowadzili java.timepakiety, które są teraz zalecaną alternatywą.

Stephen C.
źródło
To rozwiązanie działa świetnie, z tym wyjątkiem, że nie używa zerowego czasu dla godzin-minut-sekund-milisekund, jak nowa data (rok, miesiąc, dzień). Potrzebujemy więc czegoś takiego: Calendar cal = Calendar.getInstance (); cal.clear (); zestaw kal. (rok + 1900, miesiąc, data); Data dateRepresentation = cal.getTime ();
Joel Richard Koett
11

Natknąłem się na to pytanie jako duplikat nowszego pytania, które zadawało w jaki sposób nieaktualny sposób na uzyskanieDate określonego roku, miesiąca i dnia.

Dotychczasowe odpowiedzi mówią, że należy korzystać z Calendarklasy, i było to prawdą, dopóki nie pojawiła się Java 8. Ale od wersji Java 8 standardowy sposób to:

LocalDate localDate = LocalDate.of(1985, 1, 1);

A jeśli naprawdę potrzebujesz java.util.Date, możesz skorzystać z sugestii zawartych w tym pytaniu .

Aby uzyskać więcej informacji, sprawdź interfejs API lub samouczki dotyczące języka Java 8.

Kevin Workman
źródło
7

Należy pamiętać, że Calendar.getTime()jest to niedeterministyczne w tym sensie, że pora dnia część domyślnie przyjmuje bieżącą godzinę.

Aby odtworzyć, spróbuj uruchomić następujący kod kilka razy:

Calendar c = Calendar.getInstance();
c.set(2010, 2, 7); // NB: 2 means March, not February!
System.err.println(c.getTime());

Wyjście np .:

Sun Mar 07 10:46:21 CET 2010

Uruchomienie tego samego kodu kilka minut później daje:

Sun Mar 07 10:57:51 CET 2010

Tak więc, chociaż set()zmusza odpowiednie pola do poprawienia wartości, przecieka czas systemowy dla pozostałych pól. (Testowane powyżej z Sun jdk6 i jdk7)

Tijn Porcelijn
źródło
2

Ponieważ konstruktor daty jest przestarzały, możesz wypróbować ten kod.

import java.util.Calendar;

  Calendar calendar = Calendar.getInstance();
     calendar.set(Calendar.HOUR_OF_DAY, 6);// for 6 hour
     calendar.set(Calendar.MINUTE, 0);// for 0 min
     calendar.set(Calendar.SECOND, 0);// for 0 sec
     calendar.set(1996,0,26);// for Date [year,month(0 to 11), date]

    Date date = new Date(calendar.getTimeInMillis());// calendar gives long value

    String mConvertedDate = date.toString();// Fri Jan 26 06:00:00 GMT+05:30 1996
Divyanshu Kumar
źródło
1
Te straszne klasy są teraz dziedzictwem, które zostały wyparte lata temu przez klasy java.time zdefiniowane w JSR 310. Sugerowanie Datei Calendarw 2019 roku jest kiepską radą.
Basil Bourque,
@BasilBourque dzięki za sugestię, czekam na aktualizację wkrótce.
Divyanshu Kumar,
1

Możesz stworzyć metodę taką jak new Date(year,month,date)w kodzie, używając Calendarklasy.

private Date getDate(int year,int month,int date){
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, year);
    cal.set(Calendar.MONTH, month-1);
    cal.set(Calendar.DAY_OF_MONTH, day);
    return cal.getTime();
}

Będzie działać tak, jak przestarzały konstruktor Date

Sudhanshu Dubey
źródło
1
Te strasznie kłopotliwe klasy zostały wyparte lata temu przez nowoczesne klasy java.time , Instanta konkretnie i ZonedDateTime.
Basil Bourque,
Powyższe rozwiązanie powinno dodać rok 1900 i NIE powinno odejmować 1 od miesiąca, a także powinno uruchomić cal.clear () po wierszu Calendar.getInstance (), aby godziny / minuty / sekundy / milisekundy zostały wyzerowane (jak robią to w Konstruktorze daty).
Joel Richard Koett
1

Konstruktor daty oczekuje lat w formacie lat od 1900 r., Miesięcy zerowych, dni opartych na jednym, i ustawia godziny / minuty / sekundy / milisekundy na zero.

Date result = new Date(year, month, day);

Tak więc używając przestarzałego kalendarza (lata zerowe, miesiące zerowe, dni bazowe) dla przestarzałego konstruktora daty, potrzebujemy czegoś takiego:

Calendar calendar = Calendar.getInstance();
calendar.clear(); // Sets hours/minutes/seconds/milliseconds to zero
calendar.set(year + 1900, month, day);
Date result = calendar.getTime();

Lub używając Java 1.8 (który ma rok zerowy oraz miesiące i dni oparte na jednym):

Date result = Date.from(LocalDate.of(year + 1900, month + 1, day).atStartOfDay(ZoneId.systemDefault()).toInstant());

Oto równe wersje Date, Calendar i Java 1.8:

int year = 1985; // 1985
int month = 1; // January
int day = 1; // 1st

// Original, 1900-based year, zero-based month, one-based day
Date date1 = new Date(year - 1900, month - 1, day);

// Calendar, zero-based year, zero-based month, one-based day
Calendar calendar = Calendar.getInstance();
calendar.clear(); // Sets hours/minutes/seconds/milliseconds to zero
calendar.set(year, month - 1, day);
Date date2 = calendar.getTime();

// Java-time back to Date, zero-based year, one-based month, one-based day
Date date3 = Date.from(LocalDate.of(year, month, day).atStartOfDay(ZoneId.systemDefault()).toInstant());

SimpleDateFormat format = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss.SSS");

// All 3 print "1985-Jan-01 00:00:00.000"
System.out.println(format.format(date1));
System.out.println(format.format(date2));
System.out.println(format.format(date3));
Joel Richard Koett
źródło
1
Obie te straszne klasy zostały wyparte lata temu przez klasy java.time zdefiniowane w JSR 310.
Basil Bourque
Zaktualizowana odpowiedź w celu wyświetlenia wersji Data, Kalendarz i Java 1.8.
Joel Richard Koett,
0
new GregorianCalendar(1985, Calendar.JANUARY, 1).getTime();

(sposób wcześniejszy niż Java-8)

Curtis Yallop
źródło
To chyba najlepsze, co możesz zrobić bez java.time. Jednak również przed wersją Java 8 możesz używać java.time, istnieje backport: biblioteka ThreeTen Backport .
Ole VV,