I didn't design or write Java's Date-Time API, so I can't definitively say "why did they do it this way." I can however suggest two key reasons they should do it this way:
- Expressive power
- Parallel form
First, consider the context: Java's dates and time code is a large, complex package dealing with a very tricky subject matter. As common as "time" is, implementing the concept involves dozens of interpretations and formats, with variations across geographic locations (timezones), languages, calendar systems, clock resolutions, time scales, numerical representations, data sources, and intervals. Corrective deltas (e.g. leap years/days) are common, and can be irregularly inserted at any time (leap seconds). Yet the package is a core feature, likely to be widely used, and it's expected to deal with all those variations both correctly and precisely. It's a tall order. The result, not surprisingly, is a complex API including many classes, each with many methods.
Now, why use of
methods rather than a "regular" class constructor? For example, why LocalDate.of(...)
, LocalDate.ofEpochDay(...)
, and LocalDate.ofYearDay(...)
rather than just a handful of new LocalDate(...)
calls?
First, expressive power: Individual named methods express the intent of the construction and the form of the data being consumed.
Assume for a moment that Java's method overloading is sufficient to allow the variety of constructors you want. (Without getting into a language-feature discussion around duck typing and single vs. dynamic vs. multiple dispatch I'll just say: sometimes this is true, sometimes not. For now, just assume there is no technical limitation.)
It might be possible for constructor documentation to state "when passed only a single long
value, that value is interpreted as an epoch day count, not a year." If the low-level data types passed to the other constructors is different (e.g. only the epoch case uses long
, and all the other constructors use int
), you might get away with this.
If you looked at code calling a LocalDate
constructor with a single long
, it'd look very similar to code in which a year is passed in, especially with passing a year arguably the most common case. Having that common constructor might be possible, but it wouldn't necessarily lead to great clarity.
The API explicitly calling out ofEpochDay
however, signals a clear difference in units and intent. Similarly LocalDate.ofYearDay
signals that the common month
parameter is being skipped, and that the day parameter, while still an int
, is a dayOfYear
not a dayOfMonth
. Explicit constructor methods are intended to express what's going on with greater clarity.
Dynamically- and duck-typed languages such as Python and JavaScript have other means to signal alternate interpretations (e.g. optional parameters and out-of-band sentinel values), but even Python and JS developers often use distinguished method names to signal different interpretations. It's even more common in Java, given both its technical constraints (it's a statically typed, single-dispatch language) and its cultural conventions/idioms.
Second, parallel form. LocalDate
could provide a standard Java constructor in addition to, or in place of, its two of
methods. But to what end? It still needs ofEpochDay
and ofDayYear
for those use cases. Some classes do provide both constructors and the equivalent of ofXYZ
methods--for example, both Float('3.14')
and Float.parseFloat('3.14')
exist. But if you need ofXYZ
constructors for some things, why not use them all the time? That is simpler, more regular, and more parallel. As an immutable type, the majority of LocalDate
methods are constructors. There are seven major groups of methods that construct new instances (respectively, the at
, from
, minus
, of
, parse
, plus
, and with
prefixes). Of LocalDate
's 60+ methods, 35+ are de facto constructors. Only 2 of those could be easily converted into standard class-based constructors. Does it really make sense to special-case those 2 in a way not parallel to all the others? Would client code using those two special case constructors be notably clearer or simpler? Probably not. It might even make client code more varied and less straightforward.