First about this code:
static void addElements(Object[] objArr) { objArr[0] = new Pair<Integer,Integer>(0,0); objArr[1] = new Pair<String,String>("","");
Here you pass an argument of type Object[] to addElements . Therefore, the compiler will allow you to add everything that is Object . Even this code will also compile:
static void addElements(Object[] objArr) { objArr[0] = new Pair<Integer,Integer>(0,0); objArr[1] = new Pair<String,String>("",""); objArr[2] = new Date();
However, you will get runtime exceptions, since generic types are compile-time and runtime checks.
Now your question is, why even allow raw types in generics?
One of the reasons that it is allowed to have backward compatibility with older JVMs, as well as in cases where the interface designer may not know all the types that can be provided at run time. Your error-1 needs to be cast from raw types to specific types:
// this should compile @SuppressWarnings("unchecked") Pair<Integer, Integer> pair = (Pair<Integer, Integer>) intPairArr[0]; // NO error -1
EDIT:
About the wildcards dilemma:
Let's look at a very simple example of using unlimited wildcards:
Pair<?, ?> intPair = new Pair<Integer, Integer>(4, 9); Object val2 = intPair.getSecond(); System.out.printf("val2: %d, isInt: %s%n", val2, (val2 instanceof Integer)); intPair.setFirst( null );
It will compile and run and produce this expected result:
val2: 9, isInt: true
However, this does not compile:
intPair.setSecond((Object) new Integer(10));
In a parameterized unbounded wildcard type, such as Pair<?,?> , The field type and return method types will be unknown , that is, both fields will be of type ? . ? setter methods accept a type argument ? and getter methods return ? .
In this situation, the compiler will not allow you to assign something to a field or pass anything to setter methods. The reason is that the compiler cannot make sure that the object that we are trying to pass as an argument to the set method has the expected type, because the expected type is unknown.
In contrast, getter methods can be called and return an object of an unknown type, which we can assign to a reference variable of type Object.
So, you are right that this limits its use, as seen in the small example above, where values ββcan be assigned at build time, but not when trying to call setter methods.
However, you can increase the usefulness of your code by using wildcards with a lower type , for example:
Pair<? super Object, ? super Object> intPair = new Pair<Object, Object>(4, 9); Object val2 = intPair.getSecond(); System.out.printf("val2: %s, isInt: %s%n", val2, (val2 instanceof Integer)); intPair.setSecond(10); val2 = intPair.getSecond(); System.out.printf("val2: %s, isInt: %s%n", val2, (val2 instanceof Integer));
Now this not only compiles, but also starts with the expected results:
val2: 9, isInt: true val2: 10, isInt: true
About your second question: I quote a couple directly from your related article:
Using arrays of raw types or unlimited substitution parameters with parameters, we issue checks of a static type that a homogeneous sequence to come with. As a result, we must use explicit casts or risk an unexpected ClassCastException s. In the case of an unlimited parameterized wildcard type, we are further limited by how we can use array elements, since the compiler prevents certain operations with an unlimited parametric wildcard. In essence, arrays of raw types and unlimited substitution parameters are semantically very different from what we will express using an array of a specific substitution parameter. For this reason, they are not a good solution and acceptable only when the superior performance of arrays (compared to collections) is paramount.
The author emphasizes that unlimited wildcards in arrays are not a good workaround due to its limitations and excellent efficiency only in the context of arrays vs collections .