The Java Legacy is Constantly Growing
I’ve recently stumbled upon a very interesting caveat of the JDK APIs, the Class.getConstructors()
method. Its method signature is this:
Constructor<?>[] getConstructors()
The interesting thing here is that Class.getConstructor(Class...)
returns a Constructor<T>
, with <T>
being maintained:
Constructor<T> getConstructor(Class<?>... parameterTypes)
Why is there a difference, i.e. why doesn’t the first method return Constructor<T>[]
?
Let’s consider the Javadoc:
Note that while this method returns an array of Constructor<T> objects (that is an array of constructors from this class), the return type of this method is Constructor<?>[] and not Constructor<T>[] as might be expected. This less informative return type is necessary since after being returned from this method, the array could be modified to hold Constructor objects for different classes, which would violate the type guarantees of Constructor<T>[].
That’s a tough one. Historically, here’s how this happened:
Java 1.0 / Oak: Arrays
In Java 1.0 (the immediate successor of the Oak programming language), arrays were already introduced. In fact, they have been introduced before the collections API, which was introduced in Java 1.2. Arrays suffer from all the problems that we know today, including them being covariant, which leads to a lot of problems at runtime, that cannot be checked at compile time:
Object[] objects = new String[1]; objects[0] = Integer.valueOf(1); // Ouch
Java 1.1: Reflection API
Short of a “decent” collections API, the only possible return type of the Class.getConstructors()
method was Constructor[]
. A reasonable decision at the time. Of course, you could do the same mistake as above:
Object[] objects = String.class.getConstructors(); objects[0] = Integer.valueOf(1); // Ouch
but in the addition to the above, you could also, rightfully, write this:
Constructor[] constructors = String.class.getConstructors(); constructors[0] = Object.class.getConstructor(); // Muahahahahahahaha
Java 1.2: Collections API
Java has been backwards-compatible from the very early days, even from Oak onwards. There’s a very interesting piece of historic research about some of Oak’s backwards-compatibility having leaked into Java to this date in this Stack Overflow question.
While it would have been natural to design the reflection API using collections, now, it was already too late. A better solution might’ve been:
List getConstructors()
However, note that we didn’t have generics yet, so the array actually conveys more type information than the collection.
Java 1.5: Generics
In Java 5, the change from
Constructor[] getConstructors()
to
Constructor<?>[] getConstructors()
has been made for the reasons mentioned above. Now, the alternative API using a collection would definitely have been better:
List<Constructor<T>> getConstructors()
But the ship has sailed.
Java, the ugly wart
Java is full of these little caveats. They’re all documented in the Javadocs, and often on Stack Overflow. Just yesterday, we’ve documented a new caveat related to completely new API in Map
and ConcurrentHashMap
.
“Stewardship: the Sobering Parts,” a very good talk about all those caveats and how hard it is to maintain them by Brian Goetz can be seen here:
The summary of the talk:
When language designers talk about the language they’re designing
Reference: | The Java Legacy is Constantly Growing from our JCG partner Lukas Eder at the JAVA, SQL, AND JOOQ blog. |