Summary
Change the type of primitives fields in java.time implementation classes to enable a more compact representation. The serialized form of both instances and the classes themselves remain compatible.
Problem
Some java.time classes use larger primitive field types than necessary, wasting memory and preventing more compact representations.
In the classes LocalDate, YearMonth, MonthDay, and HijrahDate int or short fields are used even when the range of the values for months and days is 1..31 and 1..12 respectively.
Serialization compatibility of both instances and the class objects is essential. Compatibility of java.time instances is not an issue, all classes use serialization proxies with dedicated serial forms.
For classes, the serialization specification for classes includes the declared type of the classes fields and that must be identical to be compatible.
Solution
The serialization API provides flexibility in the declaration of the serialized fields using the serialPersistentFields mechanism. It allows the class to explicitly declare the names and types of each serialized field.
The use of javadoc tags for @serialFields allows the specification of the serialized form to be provided with the SerialPersistentFields.
Specification
There is no change to the specified serialized form of either the java.time class objects or their instances.
The changes to the implementation primitive fields types does not change the specification.
Javadoc and APIdiff are attached.
The raw to LocaDate, MonthYear, YearMonth, and HijrahDate are:
diff --git a/src/java.base/share/classes/java/time/LocalDate.java b/src/java.base/share/classes/java/time/LocalDate.java
index 016bdab5394..b5204c6e61d 100644
--- a/src/java.base/share/classes/java/time/LocalDate.java
+++ b/src/java.base/share/classes/java/time/LocalDate.java
@@ -79,6 +79,8 @@
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
+import java.io.ObjectStreamField;
+import java.io.Serial;
import java.io.Serializable;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.IsoEra;
@@ -142,6 +144,22 @@
public final class LocalDate
implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable {
+ /**
+ * For backward compatibility of the serialized {@code LocalDate.class} object,
+ * explicitly declare the types of the serialized fields as defined in JDK 1.8.
+ * Instances of {@code LocalDate} are serialized using the dedicated
+ * serialized form by {@code writeReplace}.
+ * @serialField year int The year.
+ * @serialField month short The month-of-year.
+ * @serialField day short The day-of-month.
+ */
+ @Serial
+ private static final ObjectStreamField[] serialPersistentFields = {
+ new ObjectStreamField("year", int.class),
+ new ObjectStreamField("month", short.class),
+ new ObjectStreamField("day", short.class)
+ };
+
/**
* The minimum supported {@code LocalDate}, '-999999999-01-01'.
* This could be used by an application as a "far past" date.
@@ -178,15 +196,15 @@
/**
* @serial The year.
*/
- private final int year;
+ private final transient int year;
/**
* @serial The month-of-year.
*/
- private final short month;
+ private final transient byte month;
/**
* @serial The day-of-month.
*/
- private final short day;
+ private final transient byte day;
//-----------------------------------------------------------------------
/**
@@ -490,8 +508,8 @@ private static LocalDate resolvePreviousValid(int year, int month, int day) {
*/
private LocalDate(int year, int month, int dayOfMonth) {
this.year = year;
- this.month = (short) month;
- this.day = (short) dayOfMonth;
+ this.month = (byte) month;
+ this.day = (byte) dayOfMonth;
}
//-----------------------------------------------------------------------
diff --git a/src/java.base/share/classes/java/time/MonthDay.java b/src/java.base/share/classes/java/time/MonthDay.java
index 1de4fa84d3e..2c89202a886 100644
--- a/src/java.base/share/classes/java/time/MonthDay.java
+++ b/src/java.base/share/classes/java/time/MonthDay.java
@@ -69,6 +69,8 @@
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
+import java.io.ObjectStreamField;
+import java.io.Serial;
import java.io.Serializable;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
@@ -133,6 +135,20 @@
*/
@java.io.Serial
private static final long serialVersionUID = -939150713474957432L;
+
+ /**
+ * For backward compatibility of the serialized {@code MonthDay.class} object,
+ * explicitly declare the types of the serialized fields as defined in JDK 1.8.
+ * Instances of {@code MonthDay} are serialized using the dedicated
+ * serialized form by {@code writeReplace}.
+ * @serialField month int The month-of-year.
+ * @serialField day int The day-of-month.
+ */
+ @Serial
+ private static final ObjectStreamField[] serialPersistentFields = {
+ new ObjectStreamField("month", int.class),
+ new ObjectStreamField("day", int.class)
+ };
/**
* Parser.
*/
@@ -144,13 +160,13 @@
.toFormatter();
/**
- * @serial The month-of-year, not null.
+ * @serial The month-of-year.
*/
- private final int month;
+ private final transient byte month;
/**
* @serial The day-of-month.
*/
- private final int day;
+ private final transient byte day;
//-----------------------------------------------------------------------
/**
@@ -319,8 +335,8 @@ public static MonthDay parse(CharSequence text, DateTimeFormatter formatter) {
* @param dayOfMonth the day-of-month to represent, validated from 1 to 29-31
*/
private MonthDay(int month, int dayOfMonth) {
- this.month = month;
- this.day = dayOfMonth;
+ this.month = (byte) month;
+ this.day = (byte) dayOfMonth;
}
//-----------------------------------------------------------------------
diff --git a/src/java.base/share/classes/java/time/YearMonth.java b/src/java.base/share/classes/java/time/YearMonth.java
index 8ad1172811f..1d648b0f2ea 100644
--- a/src/java.base/share/classes/java/time/YearMonth.java
+++ b/src/java.base/share/classes/java/time/YearMonth.java
@@ -78,6 +78,8 @@
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
+import java.io.ObjectStreamField;
+import java.io.Serial;
import java.io.Serializable;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
@@ -137,6 +139,20 @@
*/
@java.io.Serial
private static final long serialVersionUID = 4183400860270640070L;
+
+ /**
+ * For backward compatibility of the serialized {@code YearMonth.class} object,
+ * explicitly declare the types of the serialized fields as defined in JDK 1.8.
+ * Instances of {@code YearMonth} are serialized using the dedicated
+ * serialized form by {@code writeReplace}.
+ * @serialField year int The year.
+ * @serialField month int The month-of-year.
+ */
+ @java.io.Serial
+ private static final ObjectStreamField[] serialPersistentFields = {
+ new ObjectStreamField("year", int.class),
+ new ObjectStreamField("month", int.class),
+ };
/**
* Parser.
*/
@@ -149,11 +165,11 @@
/**
* @serial The year.
*/
- private final int year;
+ private final transient int year;
/**
- * @serial The month-of-year, not null.
+ * @serial The month-of-year..
*/
- private final int month;
+ private final transient byte month;
//-----------------------------------------------------------------------
/**
@@ -306,7 +322,7 @@ public static YearMonth parse(CharSequence text, DateTimeFormatter formatter) {
*/
private YearMonth(int year, int month) {
this.year = year;
- this.month = month;
+ this.month = (byte) month;
}
/**
diff --git a/src/java.base/share/classes/java/time/chrono/HijrahDate.java b/src/java.base/share/classes/java/time/chrono/HijrahDate.java
index 114a47e4797..2d3e4f93e69 100644
--- a/src/java.base/share/classes/java/time/chrono/HijrahDate.java
+++ b/src/java.base/share/classes/java/time/chrono/HijrahDate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -137,11 +137,11 @@
/**
* The month-of-year.
*/
- private final transient int monthOfYear;
+ private final transient byte monthOfYear;
/**
* The day-of-month.
*/
- private final transient int dayOfMonth;
+ private final transient byte dayOfMonth;
//-------------------------------------------------------------------------
/**
@@ -273,8 +273,8 @@ private HijrahDate(HijrahChronology chrono, int prolepticYear, int monthOfYear,
this.chrono = chrono;
this.prolepticYear = prolepticYear;
- this.monthOfYear = monthOfYear;
- this.dayOfMonth = dayOfMonth;
+ this.monthOfYear = (byte) monthOfYear;
+ this.dayOfMonth = (byte) dayOfMonth;
}
/**
@@ -287,8 +287,8 @@ private HijrahDate(HijrahChronology chrono, long epochDay) {
this.chrono = chrono;
this.prolepticYear = dateInfo[0];
- this.monthOfYear = dateInfo[1];
- this.dayOfMonth = dateInfo[2];
+ this.monthOfYear = (byte) dateInfo[1];
+ this.dayOfMonth = (byte) dateInfo[2];
}
- csr of
-
JDK-8371732 [redo] Change java.time month/day field types to 'byte'
-
- Resolved
-