0

Consider the following example, ignoring the reason one would want to do this:

private static class Original {
    public String getValue() {
        return "Foo";
    }
}

private static class Wrapper extends Original {
    private Original orig;

    public Wrapper(Original orig) {
        this.orig = orig;
    }

    @Override
    public String getValue() {
        return orig.getValue();
    }
}

public static void test(Original... o) {
    if (o != null && o.length > 0) {
        for (int i = 0; i < o.length; i++) {
            if (o[i] instanceof Wrapper) {
                o[i] = ((Wrapper) o[i]).orig; // Throws java.lang.ArrayStoreException at runtime
            }
        }
    }
}

public static void main(String[] args){
    test(new Wrapper[] { // Explicitly create an array of subclass type
        new Wrapper(new Original())
    });
}

This example gives no warnings or errors at compile-time. It seems like the compiler decides that an Wrapper[] contains Wrapper instances, which effectively means that those are definitely instances of Original class. This is perfectly fine.

However, at runtime, the Wrapper[] instance is directly passed into the method. I have thought that it would be smart enough to tear down this array and re-create an instance of Original[] at runtime, but it seems like this is not the case.

Is this behavior ever documented somewhere (like JLS)? An ordinary programmer like me will always assume that I can manipulate that vararg parameter of Original... as if it is an Original[].

Jai
  • 8,165
  • 2
  • 21
  • 52
  • So what's the exact question? – Hearen Jul 03 '18 at 06:11
  • 1
    This problem is the reason why Java generics are invariant (e.g. `List` is not a `List`). – Andy Turner Jul 03 '18 at 06:17
  • 1
    [JLS Sec 4.10.3](https://docs.oracle.com/javase/specs/jls/se9/html/jls-4.html#jls-4.10.3): "If S and T are both reference types, then S[] >1 T[] iff S >1 T." – Andy Turner Jul 03 '18 at 06:20
  • I'm kind of confused that the type of `o` is `Wrapper[]` and not `Original[]` Shouldn't the passed parameter get placed inside a varargs array? Or is there a special exception for arrays which match the type of the parameter? – markspace Jul 03 '18 at 06:33
  • 1
    No, @markspace, since an array of `Wrapper` is not an `Original`, you cannot place it inside an array of `Original`. From other reference types you may know that the runtime type of an object can be more precise than the declared type of the reference variable. This is possible for arrays too: the declared type of `o` is array of `Original`, but the runtime type is array of `Wrapper`. – Ole V.V. Jul 03 '18 at 06:38
  • It's kinda weird though that it's acceptable syntax. I just checked it and `Object...` accepts any array of references, so it's consistent like that, but a bit odd. An array of `Integer` gets passed as itself, not as an `Object[]` containing one reference to an `Integer[]`. – markspace Jul 03 '18 at 06:44
  • Ha! Thanks everyone. I kept thinking it is a problem relating to varargs. So in the end, it's simply because I didn't understand arrays enough. – Jai Jul 03 '18 at 07:09

1 Answers1

1

Yes, when a Wrapper is an Original, then also a Wrapper[] is an Original[] (it surprised me too when I realized it).

Your Wrapper is a subtype of Original since it exteds the Original class.

And yes, the subtype relationship between the array types may give rise to an ArrayStoreException if the called method tries to store an Original that is not a Wrapper into the passed array. But this is not checked at compile time. It is my understanding that this is exactly why we have the ArrayStoreException type since usually other attempts to store the wrong type into an array are caught at compile time. There is a nice brief example in the documentation of ArrayStoreException. That example also demonstrates that it hasn’t really got anything to do with varargs or method calls, its for all arrays.

The Java language was designed this way from version 1 (which is long before varargs were introduced, BTW). Thanks to Andy Turner for finding the Java Language Specification (JLS) reference: It is in section 4.10.3 Subtyping among Array Types:

If S and T are both reference types, then S[] >_1 T[] iff S >_1 T.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Yup, I was too obsessed with the fact that it happened in a vararg context that it didn't occur to me that this is simply a problem from arrays. I will mark this answer as correct despite this question already being marked as duplicate, since I think it is nice to find the exact JLS clause to support this. – Jai Jul 03 '18 at 07:15