Why does an instance of Test<?> accept non-null objects in the constructor?

68 views Asked by At

The wildcard ? in generics represents an unknown type and accepts only null. However, in the following example, the constructor accepts (for example) a String object, even though I have declared an instance of Test<?>.

public class Test<T> {
    private T object;
    public Test(T object) {
        this.object = object;
    }
    public void set(T object) {
        this.object = object;
    }
    public static void main(String[] args) {
        Test<?> test = new Test<>("Test");    // compiles fine
        //test.set("Test");    // compiler error
    }
}

Why does this compile normally?

1

There are 1 answers

0
John Bollinger On BEST ANSWER

the constructor accepts (for example) a String object, even though I have declared an instance of Test<?>.

No. You have declared a variable of type Test<?>. You have created an instance of type Test<X>, for some X that the compiler is obliged to figure out for itself such that the overall statement is valid. That is, here:

        Test<?> test = new Test<>("Test");    // compiles fine

... the <> does not mean "copy the type parameter from the type of the variable" or anything much like that. Test<?> is not the type of the expression new Test<>("Test"). That the chosen type parameter must make the type assignable to type Test<?> is one of the constraints that must be satisfied, but that's trivial, because any X will do for that.

Formally, Java will infer the most specific type parameter that satisfies all the constraints for the statement to be valid. new Test<X>("Test") is valid only for a relatively few Xs:

  • java.lang.Object
  • java.lang.Serializable
  • java.lang.Comparable<String> and a few other variations on Comparable
  • java.lang.CharSequence
  • java.lang.constant.Constable (since Java 12)
  • java.lang.constant.ConstantDesc (since Java 12)
  • java.lang.String
  • intersection types of combinations of the above

String is the most specific of those, so it is the one chosen.

In practice, however, Java doesn't actually have to choose a specific type parameter at all. It just has to satisfy itself that there is at least one that works.


The expression test.set("Test") is a different case. It doesn't matter that the object assigned to test was inferred to have type Test<String>. Here, as under all circumstances, Java analyzes the expression based on the declared types of any variables involved. test is declared with type Test<?>. There is therefore no type whatever that Java can be confident test.set() will accept as an argument. However, the value null is compatible with every reference type, so whatever the particular type parameter of the object test references might be, null is an acceptable argument to test.set().