0

What makes it possible to add a Pair<String, String> when I'm only extending the class Number ? Does't this violate the expectation that an array should only hold elements of the same type ?

public class test {

    public static void main(String[] args) {
        
        Pair<? extends Number, ? extends Number>[] arr = new Pair[2];
        
        m1(arr);
        
        arr[1] = new Pair<Integer, Integer>(1, 1);
        
        System.out.println(arr[0].x);
        System.out.println(arr.getClass().getComponentType());
        
    }
    
    static void m1(Object[] arr) {
        arr[0] = new Pair<String, String>("test","test");
    }
    
    
}

class Pair<T, E> {
    
    public T x;
    public E y;
    
    public Pair(T x, E y) {
        this.x = x;
        this.y = y;
    }
    
}
  • I don't understand your question. `Double` and `Integer` are both [`Number`](https://docs.oracle.com/javase/8/docs/api/java/lang/Number.html)(s). Also, you could have a `Pair` - this `Pair` type is also known as a *tuple*. – Elliott Frisch Dec 04 '21 at 01:05

2 Answers2

0

We have the following situation: inheritance in Java is on multiple levels, not just on 2 levels.

  • class Object
  • class Number (extends Object)
  • class Integer extends Number
  • class Double extends Number

Relations:

  • Number, Integer, Double are Objects
  • Integer, Double are Numbers
  • Integer, Double are Objects

So an Integer IS a Number, just with some additional functionality. Same for Double. If you tell the array (define, declare) to hold Numbers, by writing either List<Number> list or List(? extends Number) list (so Number or any subclass thereof), then you can add any Number.

If you created an array of Numbers like this: arr[0] = new Double(2.0); (initialize and assign) then the new value is a Double, but Double is a Number, so the new value also is still a Number. So this is totally valid.

In your case, you create an array of Pairs of Numbers, making the whole thing a little more convoluted, but the basic principle remains the same: The "declared" type is still Number for all of them, but the assigned value is Double or Integer (which are still also Numbers). You could still sort this out later by instanceOf checks to see what different subtypes the values are, if need be.

JayC667
  • 2,418
  • 2
  • 17
  • 31
  • I updated the code. Now I can also use String instead of Double or Integer and violate the extends Number> parameter – Yasine Deghaies Dec 04 '21 at 01:33
  • Well now you changed your strategy to another problem. "upcasting" `Pair extends Number, ? extends Number>[] arr` to `Object[]` still is valid. And any `Object[]`-related operations on `arr` are now allowed by the compiler. But by doing this (writing unclean code) you will later on get `Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')` once you try to access type information on the contents of `Pair` or most class-related ops. – JayC667 Dec 04 '21 at 01:52
0

This is one example of the array covariance plus type erasure in Java.

Array covariance

Arrays in Java are covariant: if an array can handle Integer (Integer[]), and Integer is an Object, then this array can also handle Object, so it can be referenced as Object[]. Since String is also an Object, things get tricky! (read further here and here).

Type Erasure

The correctness of the generics types is checked at compile-time, and then forgotten/erased at runtime. A Pair<Double, Double> is just a Pair at runtime. That's why if you can trick the compiler, as you did introducing the m1() method, then you are ok at runtime. (read further here).

Your code explained

Your arr variable is described as Pair<? extends Number, ? extends Number>[]. But remember that variables are just a name that references some object. The actual object behind it is just an array of Pair (Pair[]).

When you call m1(), another variable is used to reference this very same array, but this time it's a more general variable that handle any array of objects (Object[]).

You see that the actual array created in the main method is compatible with both types of the variables used to reference it throughout the code.

To overcome the array covariance, you could choose to use an ArrayList (or any other Collection you wish), but then, as you see, you would still have the generic type erasure problem on the Pair objects. like this:

public static void main(String[] args) {

    List<Pair<? extends Number, ? extends Number>> arr = 
        new ArrayList<Pair<? extends Number, ? extends Number>>(); 
        
    m1(arr);
        
    arr.add(new Pair<Integer, Integer>(1, 1));
    ...               
}
    
static void m1(List arr) {
    arr.add(new Pair<String, String>("test","test"));
}

What a maze, uh?!

Rafael Odon
  • 1,211
  • 14
  • 20