Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why does "Integer [ ] x = (Integer [ ]) new Object[100]; " compile?

I compile:

Integer [ ] x = (Integer [ ]) new Object[100];

it succeeds. and I don't know Why?

like image 898
Oscar Avatar asked Sep 08 '26 10:09

Oscar


1 Answers

It compiles. But it does not run. Try it - shove that in a main method and execute it. You'll get a ClassCastException.

There are a number of things going on here. Most of this is 'programming language archeology' - none of it makes sense and none of it is particularly consistent. If your aim is to learn the language better, there's a very simple rule you should apply to your java programming:

  1. Do not use arrays. At all. For anything. Except perhaps arrays of primitive types (int[], long[], etcetera) for performance reasons. There is no point to them. They aren't faster than lists and have all sorts of wonky downsides. Including effectively broken equals and hashCode and toString implementations (in that they do unexpected things; the spec properly explains these things, but an API that does what the spec says it does, but the spec says bizarro unexpected things, well, I'll let the reader come up with a term for it. I trust it won't be a particularly nice term), they are this useless amalgamation of immutable-ish (they can neither grow nor shrink) but not actually immutable (you can set values), and more.

  2. When casting arrays, assume that the compiler has no idea and you'll have to rely on runtime exceptions. Really, once you've reached this level (You're casting arrays), you need to back up and fix whatever got you into that mess.

There is really not much point in going any further if your aim is to be a good java coder.

But, hey, you asked - so, let's put on our brown hat, grab a whip, play the Indiana Jones themesong, and go spelunkin' into the depths of java's distant past!

Covariance, Contravariance, Invariance.

We need to cover the key problem first. An answer has already been posted by somebody who does not understand type systems, so, that's anecdotal proof this needs to be explained first.

Java's type system is itself 'covariant'. That means: Any type is a fine substitution for any of its supertypes. If you have a method that requires that you pass it a Number parameter, you can pass an expression of type Integer, no problem.

However, once you move up to listy things (be it generics, or arrays) this does not work. They should be invariant - Only the exact type required will do and no other type can substitute.

That's because you can actually both write and read from listy things and therein lies the problem. Here's a trivial example:

Integer i = 5;
Number n = i; // this is fine.

Integer[] is = new Integer[10];
Number[] ns = is; // fine, too? right? Maybe?
ns[0] = 5.5; // ... a double
Integer firstElement = is[0]; // BOOM!

That last line shows the problem. Because basic java types are covariant and will remain so, the ns[0] = 5.5 line has to compile - I'm assigning a Double to a place that requires a Number. Nevertheless this leads to a problem on the last line.

Generics gets this right. The generics equivalent is this:

List<Integer> is = new ArrayList<Integer>();
List<Number> ns = is; // This does not compile!

And gives you the tools you need to make nice APIs; you can ask for covariance (List<? extends Number>, where a List<Integer> is acceptable, but the compiler prevents you from writing to this list to ensure the 'whoops there is a double in my list of integers' problem cannot occur), and even contravariance (List<? super Integer>, where you can pass a List<Integer>, or List<Number>, or a List<Object>. You can write to it (any integer - which works fine regardless of which of those 3 kinds of lists is actually passed in), but the compiler prevents you from reading. Or rather, returns Object, given that List in general guarantees that whatever it stores, it's at least Object).

Here's a good tip: When you read List<? extends Number> you may be translating that: "A list, containing things whose type is Number or subtypes of Number - you know, things whose type is.. something, that extends Number". That is wrong. You're supposed to read it as: "A list, limited to contain some type that I simply do not know here (see the question mark? That means 'don't know'). All I know is: That restriction is Number or some subtype there of. It could be a list that is only supposed to store integers. Could be Doubles. Could be any Number. No idea. Everything I do with this list must be valid regardless of whether that's a List<Number> or a List<Integer> or a List<Double> or any other List<X> where X is a subtype of Number. It has to work for ALL of them or it is not valid java and the compiler will refuse it. Hence why list.add() doesn't work here - there is no value that is both Double and Integer and Number and SomeSubTypeOfNumberYouHaventEvenWrittenYet. Well, except the literal null which indeed you can actually pass, the only way to call add at all.

Arrays get this totally wrong.

Why? Well, generics is really complicated. The notion that you need 4 different types that all are List<Number> adjacent but subtly different is already quite complex:

  • List (raw/legacy type. Perhaps if java had had generics from v1.0 this would not be needed).
  • List<Number>
  • List<? extends Number>
  • List<? super Number>

and java was trying to 'keep things simple' so didn't add this. Generics didn't show up until java 1.5, over a decade later.

In some ways the designers of java were apparently (it's been a looong time, I haven't asked them) also not aware that the component type of a listy thing just cannot be covariant. Or, more likely, sometimes you want to write code that operates on arbitrary arrays and the language designers decided to just let the compiler get out of the way, turn java into more or less a dynamically typed (i.e. the compiler doesn't help you) situation where only the runtime exists to prevent problems from occurring.

Hence, you can do this stuff:

Object is = new Integer[10];
Number[] numbers = (Number[]) is;
is[0] = 5.5;

That code compiles, but when you run it.. the second line works, but the third line doesn't - fails with an ArrayStoreException. That's because arrays do know what their component type is, and thus the is[0] =.. assignment does check: Hey, what's the actual component type of the array object that the is variable is pointing at? Oh it's Integer[].. hmm, then, ArrayStoreException. It's a variant of ClassCastException in that sense.

Similarly, you can actually cast Object arrays to whatever array type you want, but the system is designed so that storing things into arrays might fail with an ArrayStoreException, but reading them never will. Because it's easier to reason about code when you know certain things aren't possible. Such as non-integers being in your Integer[] array.

To ensure that rule, when you cast an array, the runtime checks and won't let you if it means read operations could fail. Hence why arrays are covariant-ish:

Object xs = new Integer[100];
Number[] ns = (Number[]) xs; // allowed
xs = new Number[100];
Integer[] is = (Integer[]) xs; // not allowed

Where 'not allowed' means: Usually compiles fine, but at runtime results in a ClassCastException.

The only reason it works that way is that the 'allowed' line is still broken (in that you'd expect an array of type Number[] to allow you to write any Number to it, whereas here this one doesn't and will fail with an ArrayStoreEx unless it's an Integer), but allowed because it can't break when reading from it (whatever is in that array, it's at least a Number. Because all Integers are Numbers and the only thing that array can store is Integer objects). Whereas the second is not allowed, because if it had been allowed, reading from it could cause problems. ArrayStoreException exists; ArrayReadException does not.

Why was the choice made this way? At some point we've arrived at a dead end. The answer to that is: "Because the spec says so" and if you want to know why that is the case, the answer is: "Because the designers at the time wanted it that way". Any further 'but.. why?' questions should be directed directly at James Gosling and co.

like image 90
rzwitserloot Avatar answered Sep 10 '26 00:09

rzwitserloot



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!