12

In the generic class Class<T> the method getConstructors() has a return type with unknown generic type parameter instead of T. The reason for this is explainend in 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>[].

A colleague of mine and I have tried to understand that explanation. In our understanding they are basically saying that it is of unknown generic type, because some caller could put other Constructor objects into that array. Did we get that right? And if so, why would someone design an API this way. Wouldn't it be better to use the specific type and trust the programmer to use the array correctly? To us it sounds a little like "We are making a worse API because the programmer using it might try something stupid". Where lies our fallacy?

assylias
  • 321,522
  • 82
  • 660
  • 783
André Stannek
  • 7,773
  • 31
  • 52
  • 2
    Good question. If this is indeed the rationale, it is not that strong. One can argue this for every collection of items. It is up to the caller to process the result, not the callee to provide a good return type. – Willem Van Onsem Jul 01 '14 at 13:45
  • 2
    See the discussion at the bottom of [this answer](http://stackoverflow.com/a/2914636/829571) – assylias Jul 01 '14 at 13:48
  • @assylias thanks, missed that one while searching for an answer. My question kind of remains: Isn't it possible to use `Constructor[]` anyway and rely on the caller to do the right thing? And if so, why decide against it? – André Stannek Jul 01 '14 at 13:58
  • @AndréStannek You are right. It is very hard to understand the reasoning behind considering such an API choice. However, since the API method exists since JDK 1.1, the developers probably decided to stick to returning an array instead of an immutable alternative, which actually supports the reasoning provided. However, a better way would have been to provide a wrapper function that returned an immutable collection. – Ashu Pachauri Jul 01 '14 at 14:37
  • Well, backward compatibility does not explain why no additional method like `getConstructorsAsList` was added. And we are talking about a reflection feature here. I could pass *anything* as a parameter to a reflective constructor call without getting any type safety warning. Ironically, I could even pass the returned `Constructor>[]` array to a constructor which expects `Constructor[]` (for whatever `T` is) from that array… – Holger Jul 01 '14 at 15:15

2 Answers2

3

The point that was mentioned by Ashu Pachauri in the comment (namely, that the array is returned for backward compatibility) is certainly valid. And in general, arrays and generics don't play together very well. (For evidence, look for all the stackoverflow questions related to "Generic Arrays"...)

Additionally, there is a rule that an API should be easy to use and hard to misuse. In this case, this is related to the Principle of least astonishment: Someone obtaining the constructors with this method could perform a perfectly legal sequence of operations on the returned array, and in the end, receive an unexpected ClassCastException. So one could say that the fact that a Constructor<?>[] array is returned aims at a "fail-fast" behavior.

An illustrative example:

import java.lang.reflect.Constructor;

public class GetConstructorsReturnType
{
    public static void main(String[] args) throws Exception
    {
        // This causes a warning, due to the cast, but imagine
        // this was possible
        Constructor<DerivedA> constructorsA[] =
            (Constructor<DerivedA>[])DerivedA.class.getConstructors();

        // The following lines are valid due to the subtype
        // relationship, but would not be valid if constructorsA
        // was declared as "Constructor<?>"
        Constructor<? extends Base> constructors[] = constructorsA;
        constructors[0] = DerivedB.class.getConstructor();

        // This causes a ClassCastException (and would also not
        // be possible constructorsA was declared as "Constructor<?>"
        DerivedA instance = constructorsA[0].newInstance();
    }
}
class Base
{
}
class DerivedA extends Base
{
    public DerivedA()
    {
    }
}
class DerivedB extends Base
{
    public DerivedB()
    {
    }
}
Community
  • 1
  • 1
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • We are still not sure if we agree with that design decision but your answer made it better comprehensible for us. I guess it doesn't get any better than that ;-) Thanks! – André Stannek Jul 02 '14 at 07:03
2

It's the exact same reason why you are not allowed to do new Constructor<T>[], but you are allowed to do new Constructor<?>[]. You can apply your same argument and say "Wouldn't it be better to use the allow the specific type and trust the programmer to use the array correctly?" Well, Java decided no. (You can imagine that inside the getConstrucotrs method, they need to create an array of Constructor, and they cannot do new Constructor<T>[] but they can do new Constructor<?>[].)

Of course, you can make an unchecked cast of the Constructor<?>[] to the Constructor<T>[], but that will give you a warning in your code, in which case you would take responsibility for making sure it's safe. But if the getConstructors method this this unchecked cast in their code, you as the caller would never be warned about the unsafeness.

newacct
  • 119,665
  • 29
  • 163
  • 224